client/tags: improve privilege checking
- Hide fields that are uneditable, rather than disabling them - Support fragmented edit privileges (e.g. roles than can edit only some aspects of tags) - up until now the client tried to send everything at once, which resulted in errors for such cases.
This commit is contained in:
parent
b378ce7ede
commit
7022686b77
6 changed files with 122 additions and 46 deletions
|
@ -3,23 +3,51 @@
|
|||
<div class='input'>
|
||||
<ul>
|
||||
<li class='names'>
|
||||
<%= ctx.makeTextInput({text: 'Names', value: ctx.tag.names.join(' '), required: true, readonly: !ctx.canEditNames}) %>
|
||||
<% if (ctx.canEditNames) { %>
|
||||
<%= ctx.makeTextInput({
|
||||
text: 'Names',
|
||||
value: ctx.tag.names.join(' '),
|
||||
required: true,
|
||||
}) %>
|
||||
<% } %>
|
||||
</li>
|
||||
<li class='category'>
|
||||
<%= ctx.makeSelect({text: 'Category', keyValues: ctx.categories, selectedKey: ctx.tag.category, required: true, readonly: !ctx.canEditCategory}) %>
|
||||
<% if (ctx.canEditCategory) { %>
|
||||
<%= ctx.makeSelect({
|
||||
text: 'Category',
|
||||
keyValues: ctx.categories,
|
||||
selectedKey: ctx.tag.category,
|
||||
required: true,
|
||||
}) %>
|
||||
<% } %>
|
||||
</li>
|
||||
<li class='implications'>
|
||||
<%= ctx.makeTextInput({text: 'Implications', value: ctx.tag.implications.join(' '), readonly: !ctx.canEditImplications}) %>
|
||||
<% if (ctx.canEditImplications) { %>
|
||||
<%= ctx.makeTextInput({
|
||||
text: 'Implications',
|
||||
value: ctx.tag.implications.join(' '),
|
||||
}) %>
|
||||
<% } %>
|
||||
</li>
|
||||
<li class='suggestions'>
|
||||
<%= ctx.makeTextInput({text: 'Suggestions', value: ctx.tag.suggestions.join(' '), readonly: !ctx.canEditSuggestions}) %>
|
||||
<% if (ctx.canEditSuggestions) { %>
|
||||
<%= ctx.makeTextInput({
|
||||
text: 'Suggestions',
|
||||
value: ctx.tag.suggestions.join(' '),
|
||||
}) %>
|
||||
<% } %>
|
||||
</li>
|
||||
<li class='description'>
|
||||
<%= ctx.makeTextarea({text: 'Description', value: ctx.tag.description, readonly: !ctx.canEditDescription}) %>
|
||||
<% if (ctx.canEditDescription) { %>
|
||||
<%= ctx.makeTextarea({
|
||||
text: 'Description',
|
||||
value: ctx.tag.description,
|
||||
}) %>
|
||||
<% } %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% if (ctx.canEditNames || ctx.canEditCategory || ctx.canEditImplications || ctx.canEditSuggestions) { %>
|
||||
<% if (ctx.canEditAnything) { %>
|
||||
<div class='messages'></div>
|
||||
<div class='buttons'>
|
||||
<input type='submit' class='save' value='Save changes'>
|
||||
|
|
|
@ -25,6 +25,7 @@ class TagController {
|
|||
this._view = new TagView({
|
||||
tag: tag,
|
||||
section: section,
|
||||
canEditAnything: api.hasPrivilege('tags:edit'),
|
||||
canEditNames: api.hasPrivilege('tags:edit:names'),
|
||||
canEditCategory: api.hasPrivilege('tags:edit:category'),
|
||||
canEditImplications: api.hasPrivilege('tags:edit:implications'),
|
||||
|
@ -53,11 +54,21 @@ class TagController {
|
|||
_evtChange(e) {
|
||||
this._view.clearMessages();
|
||||
this._view.disableForm();
|
||||
e.detail.tag.names = e.detail.names;
|
||||
e.detail.tag.category = e.detail.category;
|
||||
e.detail.tag.implications = e.detail.implications;
|
||||
e.detail.tag.suggestions = e.detail.suggestions;
|
||||
e.detail.tag.description = e.detail.description;
|
||||
if (e.detail.names !== undefined) {
|
||||
e.detail.tag.names = e.detail.names;
|
||||
}
|
||||
if (e.detail.category !== undefined) {
|
||||
e.detail.tag.category = e.detail.category;
|
||||
}
|
||||
if (e.detail.implications !== undefined) {
|
||||
e.detail.tag.implications = e.detail.implications;
|
||||
}
|
||||
if (e.detail.suggestions !== undefined) {
|
||||
e.detail.tag.suggestions = e.detail.suggestions;
|
||||
}
|
||||
if (e.detail.description !== undefined) {
|
||||
e.detail.tag.description = e.detail.description;
|
||||
}
|
||||
e.detail.tag.save().then(() => {
|
||||
this._view.showSuccess('Tag saved.');
|
||||
this._view.enableForm();
|
||||
|
|
|
@ -4,12 +4,7 @@ const api = require('../api.js');
|
|||
const tags = require('../tags.js');
|
||||
const events = require('../events.js');
|
||||
const CommentList = require('./comment_list.js');
|
||||
|
||||
function _arraysDiffer(source1, source2) {
|
||||
return (
|
||||
[...source1].filter(value => !source2.includes(value)).length > 0 ||
|
||||
[...source2].filter(value => !source1.includes(value)).length > 0);
|
||||
}
|
||||
const misc = require('../util/misc.js');
|
||||
|
||||
class Post extends events.EventTarget {
|
||||
constructor() {
|
||||
|
@ -111,13 +106,13 @@ class Post extends events.EventTarget {
|
|||
if (this._safety !== this._orig._safety) {
|
||||
detail.safety = this._safety;
|
||||
}
|
||||
if (_arraysDiffer(this._flags, this._orig._flags)) {
|
||||
if (misc.arraysDiffer(this._flags, this._orig._flags)) {
|
||||
detail.flags = this._flags;
|
||||
}
|
||||
if (_arraysDiffer(this._tags, this._orig._tags)) {
|
||||
if (misc.arraysDiffer(this._tags, this._orig._tags)) {
|
||||
detail.tags = this._tags;
|
||||
}
|
||||
if (_arraysDiffer(this._relations, this._orig._relations)) {
|
||||
if (misc.arraysDiffer(this._relations, this._orig._relations)) {
|
||||
detail.relations = this._relations;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,16 +2,21 @@
|
|||
|
||||
const api = require('../api.js');
|
||||
const events = require('../events.js');
|
||||
const misc = require('../util/misc.js');
|
||||
|
||||
class Tag extends events.EventTarget {
|
||||
constructor() {
|
||||
super();
|
||||
this._orig = {};
|
||||
|
||||
this._origName = null;
|
||||
this._names = null;
|
||||
this._category = null;
|
||||
this._description = null;
|
||||
this._suggestions = null;
|
||||
this._implications = null;
|
||||
|
||||
this._names = [];
|
||||
this._suggestions = [];
|
||||
this._implications = [];
|
||||
|
||||
this._postCount = null;
|
||||
this._creationTime = null;
|
||||
this._lastEditTime = null;
|
||||
|
@ -48,13 +53,25 @@ class Tag extends events.EventTarget {
|
|||
}
|
||||
|
||||
save() {
|
||||
const detail = {
|
||||
names: this.names,
|
||||
category: this.category,
|
||||
description: this.description,
|
||||
implications: this.implications,
|
||||
suggestions: this.suggestions,
|
||||
};
|
||||
const detail = {};
|
||||
|
||||
// send only changed fields to avoid user privilege violation
|
||||
if (misc.arraysDiffer(this._names, this._orig._names)) {
|
||||
detail.names = this._names;
|
||||
}
|
||||
if (this._category !== this._orig._category) {
|
||||
detail.category = this._category;
|
||||
}
|
||||
if (this._description !== this._orig._description) {
|
||||
detail.description = this._description;
|
||||
}
|
||||
if (misc.arraysDiffer(this._implications, this._orig._implications)) {
|
||||
detail.implications = this._implications;
|
||||
}
|
||||
if (misc.arraysDiffer(this._suggestions, this._orig._suggestions)) {
|
||||
detail.suggestions = this._suggestions;
|
||||
}
|
||||
|
||||
let promise = this._origName ?
|
||||
api.put('/tag/' + this._origName, detail) :
|
||||
api.post('/tags', detail);
|
||||
|
@ -104,15 +121,20 @@ class Tag extends events.EventTarget {
|
|||
}
|
||||
|
||||
_updateFromResponse(response) {
|
||||
this._origName = response.names ? response.names[0] : null;
|
||||
this._names = response.names;
|
||||
this._category = response.category;
|
||||
this._description = response.description;
|
||||
this._implications = response.implications;
|
||||
this._suggestions = response.suggestions;
|
||||
this._creationTime = response.creationTime;
|
||||
this._lastEditTime = response.lastEditTime;
|
||||
this._postCount = response.usages;
|
||||
const map = {
|
||||
_origName: response.names ? response.names[0] : null,
|
||||
_names: response.names,
|
||||
_category: response.category,
|
||||
_description: response.description,
|
||||
_implications: response.implications,
|
||||
_suggestions: response.suggestions,
|
||||
_creationTime: response.creationTime,
|
||||
_lastEditTime: response.lastEditTime,
|
||||
_postCount: response.usages,
|
||||
};
|
||||
|
||||
Object.assign(this, map);
|
||||
Object.assign(this._orig, map);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -244,6 +244,12 @@ function escapeHtml(unsafe) {
|
|||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function arraysDiffer(source1, source2) {
|
||||
return (
|
||||
[...source1].filter(value => !source2.includes(value)).length > 0 ||
|
||||
[...source2].filter(value => !source1.includes(value)).length > 0);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
range: range,
|
||||
formatUrlParameters: formatUrlParameters,
|
||||
|
@ -259,4 +265,5 @@ module.exports = {
|
|||
escapeHtml: escapeHtml,
|
||||
makeCssName: makeCssName,
|
||||
splitByWhitespace: splitByWhitespace,
|
||||
arraysDiffer: arraysDiffer,
|
||||
};
|
||||
|
|
|
@ -79,13 +79,26 @@ class TagEditView extends events.EventTarget {
|
|||
this.dispatchEvent(new CustomEvent('submit', {
|
||||
detail: {
|
||||
tag: this._tag,
|
||||
names: misc.splitByWhitespace(this._namesFieldNode.value),
|
||||
category: this._categoryFieldNode.value,
|
||||
implications: misc.splitByWhitespace(
|
||||
this._implicationsFieldNode.value),
|
||||
suggestions: misc.splitByWhitespace(
|
||||
this._suggestionsFieldNode.value),
|
||||
description: this._descriptionFieldNode.value,
|
||||
|
||||
names: this._namesFieldNode ?
|
||||
misc.splitByWhitespace(this._namesFieldNode.value) :
|
||||
undefined,
|
||||
|
||||
category: this._categoryFieldNode ?
|
||||
this._categoryFieldNode.value :
|
||||
undefined,
|
||||
|
||||
implications: this._implicationsFieldNode ?
|
||||
misc.splitByWhitespace(this._implicationsFieldNode.value) :
|
||||
undefined,
|
||||
|
||||
suggestions: this._suggestionsFieldNode ?
|
||||
misc.splitByWhitespace(this._suggestionsFieldNode.value) :
|
||||
undefined,
|
||||
|
||||
description: this._descriptionFieldNode ?
|
||||
this._descriptionFieldNode.value :
|
||||
undefined,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue