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
|
@ -3,23 +3,51 @@
|
||||||
<div class='input'>
|
<div class='input'>
|
||||||
<ul>
|
<ul>
|
||||||
<li class='names'>
|
<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>
|
||||||
<li class='category'>
|
<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>
|
||||||
<li class='implications'>
|
<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>
|
||||||
<li class='suggestions'>
|
<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>
|
||||||
<li class='description'>
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<% if (ctx.canEditNames || ctx.canEditCategory || ctx.canEditImplications || ctx.canEditSuggestions) { %>
|
<% if (ctx.canEditAnything) { %>
|
||||||
<div class='messages'></div>
|
<div class='messages'></div>
|
||||||
<div class='buttons'>
|
<div class='buttons'>
|
||||||
<input type='submit' class='save' value='Save changes'>
|
<input type='submit' class='save' value='Save changes'>
|
||||||
|
|
|
@ -25,6 +25,7 @@ class TagController {
|
||||||
this._view = new TagView({
|
this._view = new TagView({
|
||||||
tag: tag,
|
tag: tag,
|
||||||
section: section,
|
section: section,
|
||||||
|
canEditAnything: api.hasPrivilege('tags:edit'),
|
||||||
canEditNames: api.hasPrivilege('tags:edit:names'),
|
canEditNames: api.hasPrivilege('tags:edit:names'),
|
||||||
canEditCategory: api.hasPrivilege('tags:edit:category'),
|
canEditCategory: api.hasPrivilege('tags:edit:category'),
|
||||||
canEditImplications: api.hasPrivilege('tags:edit:implications'),
|
canEditImplications: api.hasPrivilege('tags:edit:implications'),
|
||||||
|
@ -53,11 +54,21 @@ class TagController {
|
||||||
_evtChange(e) {
|
_evtChange(e) {
|
||||||
this._view.clearMessages();
|
this._view.clearMessages();
|
||||||
this._view.disableForm();
|
this._view.disableForm();
|
||||||
|
if (e.detail.names !== undefined) {
|
||||||
e.detail.tag.names = e.detail.names;
|
e.detail.tag.names = e.detail.names;
|
||||||
|
}
|
||||||
|
if (e.detail.category !== undefined) {
|
||||||
e.detail.tag.category = e.detail.category;
|
e.detail.tag.category = e.detail.category;
|
||||||
|
}
|
||||||
|
if (e.detail.implications !== undefined) {
|
||||||
e.detail.tag.implications = e.detail.implications;
|
e.detail.tag.implications = e.detail.implications;
|
||||||
|
}
|
||||||
|
if (e.detail.suggestions !== undefined) {
|
||||||
e.detail.tag.suggestions = e.detail.suggestions;
|
e.detail.tag.suggestions = e.detail.suggestions;
|
||||||
|
}
|
||||||
|
if (e.detail.description !== undefined) {
|
||||||
e.detail.tag.description = e.detail.description;
|
e.detail.tag.description = e.detail.description;
|
||||||
|
}
|
||||||
e.detail.tag.save().then(() => {
|
e.detail.tag.save().then(() => {
|
||||||
this._view.showSuccess('Tag saved.');
|
this._view.showSuccess('Tag saved.');
|
||||||
this._view.enableForm();
|
this._view.enableForm();
|
||||||
|
|
|
@ -4,12 +4,7 @@ const api = require('../api.js');
|
||||||
const tags = require('../tags.js');
|
const tags = require('../tags.js');
|
||||||
const events = require('../events.js');
|
const events = require('../events.js');
|
||||||
const CommentList = require('./comment_list.js');
|
const CommentList = require('./comment_list.js');
|
||||||
|
const misc = require('../util/misc.js');
|
||||||
function _arraysDiffer(source1, source2) {
|
|
||||||
return (
|
|
||||||
[...source1].filter(value => !source2.includes(value)).length > 0 ||
|
|
||||||
[...source2].filter(value => !source1.includes(value)).length > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Post extends events.EventTarget {
|
class Post extends events.EventTarget {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -111,13 +106,13 @@ class Post extends events.EventTarget {
|
||||||
if (this._safety !== this._orig._safety) {
|
if (this._safety !== this._orig._safety) {
|
||||||
detail.safety = this._safety;
|
detail.safety = this._safety;
|
||||||
}
|
}
|
||||||
if (_arraysDiffer(this._flags, this._orig._flags)) {
|
if (misc.arraysDiffer(this._flags, this._orig._flags)) {
|
||||||
detail.flags = this._flags;
|
detail.flags = this._flags;
|
||||||
}
|
}
|
||||||
if (_arraysDiffer(this._tags, this._orig._tags)) {
|
if (misc.arraysDiffer(this._tags, this._orig._tags)) {
|
||||||
detail.tags = this._tags;
|
detail.tags = this._tags;
|
||||||
}
|
}
|
||||||
if (_arraysDiffer(this._relations, this._orig._relations)) {
|
if (misc.arraysDiffer(this._relations, this._orig._relations)) {
|
||||||
detail.relations = this._relations;
|
detail.relations = this._relations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,21 @@
|
||||||
|
|
||||||
const api = require('../api.js');
|
const api = require('../api.js');
|
||||||
const events = require('../events.js');
|
const events = require('../events.js');
|
||||||
|
const misc = require('../util/misc.js');
|
||||||
|
|
||||||
class Tag extends events.EventTarget {
|
class Tag extends events.EventTarget {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
this._orig = {};
|
||||||
|
|
||||||
this._origName = null;
|
this._origName = null;
|
||||||
this._names = null;
|
|
||||||
this._category = null;
|
this._category = null;
|
||||||
this._description = null;
|
this._description = null;
|
||||||
this._suggestions = null;
|
|
||||||
this._implications = null;
|
this._names = [];
|
||||||
|
this._suggestions = [];
|
||||||
|
this._implications = [];
|
||||||
|
|
||||||
this._postCount = null;
|
this._postCount = null;
|
||||||
this._creationTime = null;
|
this._creationTime = null;
|
||||||
this._lastEditTime = null;
|
this._lastEditTime = null;
|
||||||
|
@ -48,13 +53,25 @@ class Tag extends events.EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
const detail = {
|
const detail = {};
|
||||||
names: this.names,
|
|
||||||
category: this.category,
|
// send only changed fields to avoid user privilege violation
|
||||||
description: this.description,
|
if (misc.arraysDiffer(this._names, this._orig._names)) {
|
||||||
implications: this.implications,
|
detail.names = this._names;
|
||||||
suggestions: this.suggestions,
|
}
|
||||||
};
|
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 ?
|
let promise = this._origName ?
|
||||||
api.put('/tag/' + this._origName, detail) :
|
api.put('/tag/' + this._origName, detail) :
|
||||||
api.post('/tags', detail);
|
api.post('/tags', detail);
|
||||||
|
@ -104,15 +121,20 @@ class Tag extends events.EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateFromResponse(response) {
|
_updateFromResponse(response) {
|
||||||
this._origName = response.names ? response.names[0] : null;
|
const map = {
|
||||||
this._names = response.names;
|
_origName: response.names ? response.names[0] : null,
|
||||||
this._category = response.category;
|
_names: response.names,
|
||||||
this._description = response.description;
|
_category: response.category,
|
||||||
this._implications = response.implications;
|
_description: response.description,
|
||||||
this._suggestions = response.suggestions;
|
_implications: response.implications,
|
||||||
this._creationTime = response.creationTime;
|
_suggestions: response.suggestions,
|
||||||
this._lastEditTime = response.lastEditTime;
|
_creationTime: response.creationTime,
|
||||||
this._postCount = response.usages;
|
_lastEditTime: response.lastEditTime,
|
||||||
|
_postCount: response.usages,
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(this, map);
|
||||||
|
Object.assign(this._orig, map);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -244,6 +244,12 @@ function escapeHtml(unsafe) {
|
||||||
.replace(/'/g, ''');
|
.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 = {
|
module.exports = {
|
||||||
range: range,
|
range: range,
|
||||||
formatUrlParameters: formatUrlParameters,
|
formatUrlParameters: formatUrlParameters,
|
||||||
|
@ -259,4 +265,5 @@ module.exports = {
|
||||||
escapeHtml: escapeHtml,
|
escapeHtml: escapeHtml,
|
||||||
makeCssName: makeCssName,
|
makeCssName: makeCssName,
|
||||||
splitByWhitespace: splitByWhitespace,
|
splitByWhitespace: splitByWhitespace,
|
||||||
|
arraysDiffer: arraysDiffer,
|
||||||
};
|
};
|
||||||
|
|
|
@ -79,13 +79,26 @@ class TagEditView extends events.EventTarget {
|
||||||
this.dispatchEvent(new CustomEvent('submit', {
|
this.dispatchEvent(new CustomEvent('submit', {
|
||||||
detail: {
|
detail: {
|
||||||
tag: this._tag,
|
tag: this._tag,
|
||||||
names: misc.splitByWhitespace(this._namesFieldNode.value),
|
|
||||||
category: this._categoryFieldNode.value,
|
names: this._namesFieldNode ?
|
||||||
implications: misc.splitByWhitespace(
|
misc.splitByWhitespace(this._namesFieldNode.value) :
|
||||||
this._implicationsFieldNode.value),
|
undefined,
|
||||||
suggestions: misc.splitByWhitespace(
|
|
||||||
this._suggestionsFieldNode.value),
|
category: this._categoryFieldNode ?
|
||||||
description: this._descriptionFieldNode.value,
|
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 New Issue