work in progress on custom fields
This commit is contained in:
parent
361af18384
commit
86fce404a9
29 changed files with 1088 additions and 198 deletions
333
models/fields.js
333
models/fields.js
|
@ -1,11 +1,342 @@
|
|||
'use strict';
|
||||
|
||||
const knex = require('../lib/knex');
|
||||
const hasher = require('node-object-hash')();
|
||||
const slugify = require('slugify');
|
||||
const { enforce, filterObject } = require('../lib/helpers');
|
||||
const dtHelpers = require('../lib/dt-helpers');
|
||||
const interoperableErrors = require('../shared/interoperable-errors');
|
||||
const shares = require('./shares');
|
||||
const fieldsLegacy = require('../lib/models/fields');
|
||||
const bluebird = require('bluebird');
|
||||
const validators = require('../shared/validators');
|
||||
|
||||
const allowedKeysCreate = new Set(['name', 'key', 'default_value', 'type', 'group', 'settings']);
|
||||
const allowedKeysUpdate = new Set(['name', 'key', 'default_value', 'group', 'settings']);
|
||||
const hashKeys = allowedKeysCreate;
|
||||
|
||||
const fieldTypes = {};
|
||||
|
||||
fieldTypes.text = fieldTypes.website = {
|
||||
validate: entity => {},
|
||||
addColumn: (table, name) => table.string(name),
|
||||
indexed: true,
|
||||
grouped: false
|
||||
};
|
||||
|
||||
fieldTypes.longtext = fieldTypes.gpg = {
|
||||
validate: entity => {},
|
||||
addColumn: (table, name) => table.text(name),
|
||||
indexed: false,
|
||||
grouped: false
|
||||
};
|
||||
|
||||
fieldTypes.json = {
|
||||
validate: entity => {},
|
||||
addColumn: (table, name) => table.json(name),
|
||||
indexed: false,
|
||||
grouped: false
|
||||
};
|
||||
|
||||
fieldTypes.number = {
|
||||
validate: entity => {},
|
||||
addColumn: (table, name) => table.integer(name),
|
||||
indexed: true,
|
||||
grouped: false
|
||||
};
|
||||
|
||||
fieldTypes.checkbox = fieldTypes['radio-grouped'] = fieldTypes['dropdown-grouped'] = {
|
||||
validate: entity => {},
|
||||
indexed: true,
|
||||
grouped: true
|
||||
};
|
||||
|
||||
fieldTypes['radio-enum'] = fieldTypes['dropdown-enum'] = {
|
||||
validate: entity => {
|
||||
enforce(entity.settings.options, 'Options missing in settings');
|
||||
enforce(Object.keys(entity.settings.options).includes(entity.default_value), 'Default value not present in options');
|
||||
},
|
||||
addColumn: (table, name) => table.string(name),
|
||||
indexed: true,
|
||||
grouped: false
|
||||
};
|
||||
|
||||
fieldTypes.option = {
|
||||
validate: entity => [],
|
||||
addColumn: (table, name) => table.boolean(name),
|
||||
indexed: true,
|
||||
grouped: false
|
||||
};
|
||||
|
||||
fieldTypes['date'] = fieldTypes['birthday'] = {
|
||||
validate: entity => {
|
||||
enforce(['eur', 'us'].includes(entity.settings.dateFormat), 'Date format incorrect');
|
||||
},
|
||||
addColumn: (table, name) => table.dateTime(name),
|
||||
indexed: true,
|
||||
grouped: false
|
||||
};
|
||||
|
||||
|
||||
function hash(entity) {
|
||||
return hasher.hash(filterObject(entity, hashKeys));
|
||||
}
|
||||
|
||||
async function getById(context, listId, id) {
|
||||
let entity;
|
||||
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageFields');
|
||||
|
||||
entity = await tx('custom_fields').where({list: listId, id}).first();
|
||||
if (!entity) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
const orderFields = {
|
||||
order_list: 'orderListBefore',
|
||||
order_subscribe: 'orderSubscribeBefore',
|
||||
order_manage: 'orderManageBefore'
|
||||
};
|
||||
|
||||
for (const key in orderFields) {
|
||||
if (entity[key] !== null) {
|
||||
const orderIdRow = await tx('custom_fields').where('list', listId).where(key, '>', entity[key]).orderBy(key, 'asc').select(['id']).first();
|
||||
if (orderIdRow) {
|
||||
entity[orderFields[key]] = orderIdRow.id;
|
||||
} else {
|
||||
entity[orderFields[key]] = 'end';
|
||||
}
|
||||
} else {
|
||||
entity[orderFields[key]] = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
async function list(context, listId) {
|
||||
let rows;
|
||||
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageFields');
|
||||
rows = await tx('custom_fields').where({list: listId}).select(['id', 'name', 'type', 'order_list', 'order_subscribe', 'order_manage']);
|
||||
});
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function listDTAjax(context, listId, params) {
|
||||
return await dtHelpers.ajaxListWithPermissions(
|
||||
context,
|
||||
[{ entityTypeId: 'list', requiredOperations: ['manageFields'] }],
|
||||
params,
|
||||
builder => builder
|
||||
.from('custom_fields')
|
||||
.innerJoin('lists', 'custom_fields.list', 'lists.id')
|
||||
.where('custom_fields.list', listId),
|
||||
[ 'custom_fields.id', 'custom_fields.name', 'custom_fields.type', 'custom_fields.key', 'custom_fields.order_list' ],
|
||||
{
|
||||
orderByBuilder: (builder, orderColumn, orderDir) => {
|
||||
if (orderColumn === 'custom_fields.order_list') {
|
||||
builder.orderBy(knex.raw('-custom_fields.order_list'), orderDir === 'asc' ? 'desc' : 'asc'); // This is MySQL speciality. It sorts the rows in ascending order with NULL values coming last
|
||||
} else {
|
||||
builder.orderBy(orderColumn, orderDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function serverValidate(context, listId, data) {
|
||||
const result = {};
|
||||
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageFields');
|
||||
|
||||
if (data.key) {
|
||||
const existing = await tx('custom_fields').where({
|
||||
list: listId,
|
||||
key: data.key
|
||||
}).whereNot('id', data.id).first();
|
||||
|
||||
result.key = {
|
||||
exists: !!existing
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async function _validateAndPreprocess(tx, listId, entity, isCreate) {
|
||||
enforce(entity.type === 'option' || !entity.group, 'Only option may have a group assigned');
|
||||
enforce(entity.type !== 'option' || entity.group, 'Option must have a group assigned.');
|
||||
enforce(!entity.group || await tx('custom_fields').where({list: listId, id: entity.group}).first(), 'Group field does not exist');
|
||||
enforce(entity.name, 'Name must be present');
|
||||
|
||||
const fieldType = fieldTypes[entity.type];
|
||||
enforce(fieldType, 'Unknown field type');
|
||||
|
||||
const validateErrs = fieldType.validate(entity);
|
||||
enforce(!validateErrs.length, 'Invalid field');
|
||||
|
||||
enforce(validators.mergeTagValid(entity.key), 'Merge tag is not valid.');
|
||||
|
||||
const existingWithKeyQuery = knex('custom_fields').where({
|
||||
list: listId,
|
||||
key: entity.key
|
||||
});
|
||||
if (!isCreate) {
|
||||
existingWithKeyQuery.whereNot('id', data.id);
|
||||
}
|
||||
const existingWithKey = await existingWithKeyQuery.first();
|
||||
if (existingWithKey) {
|
||||
throw new interoperableErrors.DuplicitKeyError();
|
||||
}
|
||||
|
||||
entity.settings = JSON.stringify(entity.settings);
|
||||
}
|
||||
|
||||
async function _sortIn(tx, listId, entityId, orderListBefore, orderSubscribeBefore, orderManageBefore) {
|
||||
const flds = await tx('custom_fields').where('list', listId).whereNot('id', entityId);
|
||||
|
||||
const order = {};
|
||||
for (const row of flds) {
|
||||
order[row.id] = {
|
||||
order_list: null,
|
||||
order_subscribe: null,
|
||||
order_manage: null
|
||||
};
|
||||
}
|
||||
|
||||
order[entityId] = {
|
||||
order_list: null,
|
||||
order_subscribe: null,
|
||||
order_manage: null
|
||||
};
|
||||
|
||||
function computeOrder(fldName, sortInBefore) {
|
||||
flds.sort((x, y) => x[fldName] - y[fldName]);
|
||||
const ids = flds.filter(x => x[fldName] !== null).map(x => x.id);
|
||||
|
||||
let sortedIn = false;
|
||||
let idx = 1;
|
||||
for (const id of ids) {
|
||||
if (sortInBefore === id) {
|
||||
order[entityId][fldName] = idx;
|
||||
sortedIn = true;
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
order[id][fldName] = idx;
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
if (!sortedIn && sortInBefore !== 'none') {
|
||||
order[entityId][fldName] = idx;
|
||||
}
|
||||
}
|
||||
|
||||
computeOrder('order_list', orderListBefore);
|
||||
computeOrder('order_subscribe', orderSubscribeBefore);
|
||||
computeOrder('order_manage', orderManageBefore);
|
||||
|
||||
for (const id in order) {
|
||||
await tx('custom_fields').where({list: listId, id}).update(order[id]);
|
||||
}
|
||||
}
|
||||
|
||||
async function create(context, listId, entity) {
|
||||
let id;
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageFields');
|
||||
|
||||
await _validateAndPreprocess(tx, listId, entity, true);
|
||||
|
||||
let columnName;
|
||||
if (!fieldType.grouped) {
|
||||
columnName = ('custom_' + slugify(name, '_') + '_' + shortid.generate()).toLowerCase().replace(/[^a-z0-9_]/g, '');
|
||||
}
|
||||
|
||||
const filteredEntity = filterObject(entity, allowedKeysCreate);
|
||||
filteredEntity.list = listId;
|
||||
filteredEntity.column = columnName;
|
||||
|
||||
const ids = await tx('custom_fields').insert(filteredEntity);
|
||||
id = ids[0];
|
||||
|
||||
_sortIn(tx, listId, id, entity.orderListBefore, entity.orderSubscribeBefore, entity.orderManageBefore);
|
||||
|
||||
if (columnName) {
|
||||
await knex.schema.table('subscription__' + listId, table => {
|
||||
fieldType.addColumn(table, columnName);
|
||||
if (fieldType.indexed) {
|
||||
table.index(columnName);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
async function updateWithConsistencyCheck(context, listId, entity) {
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageFields');
|
||||
|
||||
const existing = await tx('custom_fields').where({list: listId, id: entity.id}).first();
|
||||
if (!existing) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
const existingHash = hash(existing);
|
||||
if (existingHash !== entity.originalHash) {
|
||||
throw new interoperableErrors.ChangedError();
|
||||
}
|
||||
|
||||
enforce(entity.type === existing.type, 'Field type cannot be changed');
|
||||
await _validateAndPreprocess(tx, listId, entity, true);
|
||||
|
||||
await tx('custom_fields').where('id', entity.id).update(filterObject(entity, allowedKeysUpdate));
|
||||
_sortIn(tx, listId, entity.id, entity.orderListBefore, entity.orderSubscribeBefore, entity.orderManageBefore);
|
||||
});
|
||||
}
|
||||
|
||||
async function remove(context, listId, id) {
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', listId, 'manageFields');
|
||||
|
||||
const existing = await tx('custom_fields').where({list: listId, id: id}).first();
|
||||
if (!existing) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
const fieldType = fieldTypes[existing.type];
|
||||
|
||||
await tx('custom_fields').where({list: listId, id}).del();
|
||||
|
||||
if (fieldType.grouped) {
|
||||
await tx('custom_fields').where({list: listId, group: id}).del();
|
||||
|
||||
} else {
|
||||
await knex.schema.table('subscription__' + listId, table => {
|
||||
table.dropColumn(existing.column);
|
||||
});
|
||||
|
||||
await tx('segemnt_rules').where({column: existing.column}).del();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
list: bluebird.promisify(fieldsLegacy.list)
|
||||
hash,
|
||||
getById,
|
||||
list,
|
||||
listDTAjax,
|
||||
create,
|
||||
updateWithConsistencyCheck,
|
||||
remove,
|
||||
serverValidate
|
||||
};
|
|
@ -85,10 +85,9 @@ async function _getById(tx, id) {
|
|||
|
||||
|
||||
async function getById(context, id) {
|
||||
shares.enforceEntityPermission(context, 'customForm', id, 'view');
|
||||
|
||||
let entity;
|
||||
await knex.transaction(async tx => {
|
||||
shares.enforceEntityPermissionTx(tx, context, 'customForm', id, 'view');
|
||||
entity = await _getById(tx, id);
|
||||
});
|
||||
|
||||
|
@ -114,10 +113,10 @@ async function serverValidate(context, data) {
|
|||
|
||||
|
||||
async function create(context, entity) {
|
||||
await shares.enforceEntityPermission(context, 'namespace', entity.namespace, 'createCustomForm');
|
||||
|
||||
let id;
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createCustomForm');
|
||||
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
|
||||
const form = filterObject(entity, allowedFormKeys);
|
||||
|
@ -141,13 +140,13 @@ async function create(context, entity) {
|
|||
}
|
||||
|
||||
async function updateWithConsistencyCheck(context, entity) {
|
||||
await shares.enforceEntityPermission(context, 'customForm', entity.id, 'edit');
|
||||
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'customForm', entity.id, 'edit');
|
||||
|
||||
const existing = await _getById(tx, entity.id);
|
||||
|
||||
const existingHash = hash(existing);
|
||||
if (existingHash != entity.originalHash) {
|
||||
if (texistingHash !== entity.originalHash) {
|
||||
throw new interoperableErrors.ChangedError();
|
||||
}
|
||||
|
||||
|
@ -173,9 +172,9 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
}
|
||||
|
||||
async function remove(context, id) {
|
||||
shares.enforceEntityPermission(context, 'customForm', id, 'delete');
|
||||
|
||||
await knex.transaction(async tx => {
|
||||
shares.enforceEntityPermissionTx(tx, context, 'customForm', id, 'delete');
|
||||
|
||||
const entity = await tx('custom_forms').where('id', id).first();
|
||||
|
||||
if (!entity) {
|
||||
|
|
|
@ -42,10 +42,10 @@ async function getById(context, id) {
|
|||
}
|
||||
|
||||
async function create(context, entity) {
|
||||
await shares.enforceEntityPermission(context, 'namespace', entity.namespace, 'createList');
|
||||
|
||||
let id;
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createList');
|
||||
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
enforce(entity.unsubscription_mode >= 0 && entity.unsubscription_mode < UnsubscriptionMode.MAX, 'Unknown unsubscription mode');
|
||||
|
||||
|
@ -64,16 +64,16 @@ async function create(context, entity) {
|
|||
}
|
||||
|
||||
async function updateWithConsistencyCheck(context, entity) {
|
||||
await shares.enforceEntityPermission(context, 'list', entity.id, 'edit');
|
||||
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', entity.id, 'edit');
|
||||
|
||||
const existing = await tx('lists').where('id', entity.id).first();
|
||||
if (!existing) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
const existingHash = hash(existing);
|
||||
if (existingHash != entity.originalHash) {
|
||||
if (texistingHash !== entity.originalHash) {
|
||||
throw new interoperableErrors.ChangedError();
|
||||
}
|
||||
|
||||
|
@ -88,9 +88,9 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
}
|
||||
|
||||
async function remove(context, id) {
|
||||
await shares.enforceEntityPermission(context, 'list', id, 'delete');
|
||||
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'list', id, 'delete');
|
||||
|
||||
await tx('lists').where('id', id).del();
|
||||
await knex.schema.dropTableIfExists('subscription__' + id);
|
||||
});
|
||||
|
|
|
@ -118,13 +118,10 @@ async function getById(context, id) {
|
|||
|
||||
async function create(context, entity) {
|
||||
enforce(entity.namespace, 'Parent namespace must be set');
|
||||
await shares.enforceEntityPermission(context, 'namespace', entity.namespace, 'createNamespace');
|
||||
|
||||
let id;
|
||||
await knex.transaction(async tx => {
|
||||
if (!await tx('namespaces').select(['id']).where('id', entity.namespace).first()) {
|
||||
throw new interoperableErrors.DependencyNotFoundError();
|
||||
}
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createNamespace');
|
||||
|
||||
const ids = await tx('namespaces').insert(filterObject(entity, allowedKeys));
|
||||
id = ids[0];
|
||||
|
@ -138,16 +135,17 @@ async function create(context, entity) {
|
|||
|
||||
async function updateWithConsistencyCheck(context, entity) {
|
||||
enforce(entity.id !== 1 || entity.namespace === null, 'Cannot assign a parent to the root namespace.');
|
||||
await shares.enforceEntityPermission(context, 'namespace', entity.id, 'edit');
|
||||
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.id, 'edit');
|
||||
|
||||
const existing = await tx('namespaces').where('id', entity.id).first();
|
||||
if (!existing) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
const existingHash = hash(existing);
|
||||
if (existingHash != entity.originalHash) {
|
||||
if (texistingHash !== entity.originalHash) {
|
||||
throw new interoperableErrors.ChangedError();
|
||||
}
|
||||
|
||||
|
@ -175,9 +173,10 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
|
||||
async function remove(context, id) {
|
||||
enforce(id !== 1, 'Cannot delete the root namespace.');
|
||||
await shares.enforceEntityPermission(context, 'namespace', id, 'delete');
|
||||
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', id, 'delete');
|
||||
|
||||
const childNs = await tx('namespaces').where('namespace', id).first();
|
||||
if (childNs) {
|
||||
throw new interoperableErrors.ChildDetectedError();
|
||||
|
|
|
@ -36,10 +36,9 @@ async function listDTAjax(context, params) {
|
|||
}
|
||||
|
||||
async function create(context, entity) {
|
||||
await shares.enforceEntityPermission(context, 'namespace', entity.namespace, 'createReportTemplate');
|
||||
|
||||
let id;
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createReportTemplate');
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
|
||||
const ids = await tx('report_templates').insert(filterObject(entity, allowedKeys));
|
||||
|
@ -52,16 +51,16 @@ async function create(context, entity) {
|
|||
}
|
||||
|
||||
async function updateWithConsistencyCheck(context, entity) {
|
||||
await shares.enforceEntityPermission(context, 'reportTemplate', entity.id, 'edit');
|
||||
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'reportTemplate', entity.id, 'edit');
|
||||
|
||||
const existing = await tx('report_templates').where('id', entity.id).first();
|
||||
if (!existing) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
}
|
||||
|
||||
const existingHash = hash(existing);
|
||||
if (existingHash != entity.originalHash) {
|
||||
if (texistingHash !== entity.originalHash) {
|
||||
throw new interoperableErrors.ChangedError();
|
||||
}
|
||||
|
||||
|
|
|
@ -56,16 +56,12 @@ async function listDTAjax(context, params) {
|
|||
}
|
||||
|
||||
async function create(context, entity) {
|
||||
await shares.enforceEntityPermission(context, 'namespace', entity.namespace, 'createReport');
|
||||
await shares.enforceEntityPermission(context, 'reportTemplate', entity.report_template, 'execute');
|
||||
|
||||
let id;
|
||||
await knex.transaction(async tx => {
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createReport');
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'reportTemplate', entity.report_template, 'execute');
|
||||
|
||||
if (!await tx('report_templates').select(['id']).where('id', entity.report_template).first()) {
|
||||
throw new interoperableErrors.DependencyNotFoundError();
|
||||
}
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
|
||||
entity.params = JSON.stringify(entity.params);
|
||||
|
||||
|
@ -81,10 +77,10 @@ async function create(context, entity) {
|
|||
}
|
||||
|
||||
async function updateWithConsistencyCheck(context, entity) {
|
||||
await shares.enforceEntityPermission(context, 'report', entity.id, 'edit');
|
||||
await shares.enforceEntityPermission(context, 'reportTemplate', entity.report_template, 'execute');
|
||||
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'report', entity.id, 'edit');
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'reportTemplate', entity.report_template, 'execute');
|
||||
|
||||
const existing = await tx('reports').where('id', entity.id).first();
|
||||
if (!existing) {
|
||||
throw new interoperableErrors.NotFoundError();
|
||||
|
@ -93,14 +89,10 @@ async function updateWithConsistencyCheck(context, entity) {
|
|||
existing.params = JSON.parse(existing.params);
|
||||
|
||||
const existingHash = hash(existing);
|
||||
if (existingHash != entity.originalHash) {
|
||||
if (texistingHash !== entity.originalHash) {
|
||||
throw new interoperableErrors.ChangedError();
|
||||
}
|
||||
|
||||
if (!await tx('report_templates').select(['id']).where('id', entity.report_template).first()) {
|
||||
throw new interoperableErrors.DependencyNotFoundError();
|
||||
}
|
||||
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
await namespaceHelpers.validateMove(context, entity, existing, 'report', 'createReport', 'delete');
|
||||
|
||||
|
|
|
@ -83,9 +83,9 @@ async function listRolesDTAjax(context, entityTypeId, params) {
|
|||
async function assign(context, entityTypeId, entityId, userId, role) {
|
||||
const entityType = permissions.getEntityType(entityTypeId);
|
||||
|
||||
await enforceEntityPermission(context, entityTypeId, entityId, 'share');
|
||||
|
||||
await knex.transaction(async tx => {
|
||||
await enforceEntityPermissionTx(tx, context, entityTypeId, entityId, 'share');
|
||||
|
||||
enforce(await tx('users').where('id', userId).select('id').first(), 'Invalid user id');
|
||||
enforce(await tx(entityType.entitiesTable).where('id', entityId).select('id').first(), 'Invalid entity id');
|
||||
|
||||
|
@ -427,28 +427,37 @@ function enforceGlobalPermission(context, requiredOperations) {
|
|||
throwPermissionDenied();
|
||||
}
|
||||
|
||||
async function _checkPermission(context, entityTypeId, entityId, requiredOperations) {
|
||||
if (context.user.admin) { // This handles the getAdminContext() case
|
||||
return true;
|
||||
}
|
||||
|
||||
async function _checkPermissionTx(tx, context, entityTypeId, entityId, requiredOperations) {
|
||||
const entityType = permissions.getEntityType(entityTypeId);
|
||||
|
||||
if (typeof requiredOperations === 'string') {
|
||||
requiredOperations = [ requiredOperations ];
|
||||
if (context.user.admin) { // This handles the getAdminContext() case. In this case we don't check the permission, but just the existence.
|
||||
const existsQuery = tx(entityType.entitiesTable);
|
||||
|
||||
if (entityId) {
|
||||
existsQuery.where('id', entityId);
|
||||
}
|
||||
|
||||
const exists = await existsQuery.first();
|
||||
|
||||
return !!exists;
|
||||
|
||||
} else {
|
||||
if (typeof requiredOperations === 'string') {
|
||||
requiredOperations = [ requiredOperations ];
|
||||
}
|
||||
|
||||
const permsQuery = tx(entityType.permissionsTable)
|
||||
.where('user', context.user.id)
|
||||
.whereIn('operation', requiredOperations);
|
||||
|
||||
if (entityId) {
|
||||
permsQuery.andWhere('entity', entityId);
|
||||
}
|
||||
|
||||
const perms = await permsQuery.first();
|
||||
|
||||
return !!perms;
|
||||
}
|
||||
|
||||
const permsQuery = knex(entityType.permissionsTable)
|
||||
.where('user', context.user.id)
|
||||
.whereIn('operation', requiredOperations);
|
||||
|
||||
if (entityId) {
|
||||
permsQuery.andWhere('entity', entityId);
|
||||
}
|
||||
|
||||
const perms = await permsQuery.first();
|
||||
|
||||
return !!perms;
|
||||
}
|
||||
|
||||
async function checkEntityPermission(context, entityTypeId, entityId, requiredOperations) {
|
||||
|
@ -456,23 +465,55 @@ async function checkEntityPermission(context, entityTypeId, entityId, requiredOp
|
|||
return false;
|
||||
}
|
||||
|
||||
return await _checkPermission(context, entityTypeId, entityId, requiredOperations);
|
||||
let result;
|
||||
await knex.transaction(async tx => {
|
||||
result = await _checkPermissionTx(tx, context, entityTypeId, entityId, requiredOperations);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
async function checkTypePermission(context, entityTypeId, requiredOperations) {
|
||||
return await _checkPermission(context, entityTypeId, null, requiredOperations);
|
||||
let result;
|
||||
await knex.transaction(async tx => {
|
||||
result = await _checkPermissionTx(tx, context, entityTypeId, null, requiredOperations);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
async function enforceEntityPermission(context, entityTypeId, entityId, requiredOperations) {
|
||||
const perms = await checkEntityPermission(context, entityTypeId, entityId, requiredOperations);
|
||||
if (!perms) {
|
||||
if (!entityId) {
|
||||
throwPermissionDenied();
|
||||
}
|
||||
await knex.transaction(async tx => {
|
||||
const result = await _checkPermissionTx(tx, context, entityTypeId, entityId, requiredOperations);
|
||||
if (!result) {
|
||||
throwPermissionDenied();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function enforceEntityPermissionTx(tx, context, entityTypeId, entityId, requiredOperations) {
|
||||
if (!entityId) {
|
||||
throwPermissionDenied();
|
||||
}
|
||||
const result = await _checkPermissionTx(tx, context, entityTypeId, entityId, requiredOperations);
|
||||
if (!result) {
|
||||
throwPermissionDenied();
|
||||
}
|
||||
}
|
||||
|
||||
async function enforceTypePermission(context, entityTypeId, requiredOperations) {
|
||||
const perms = await checkTypePermission(context, entityTypeId, requiredOperations);
|
||||
if (!perms) {
|
||||
await knex.transaction(async tx => {
|
||||
const result = await _checkPermissionTx(tx, context, entityTypeId, null, requiredOperations);
|
||||
if (!result) {
|
||||
throwPermissionDenied();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function enforceTypePermissionTx(tx, context, entityTypeId, requiredOperations) {
|
||||
const result = await _checkPermissionTx(tx, context, entityTypeId, null, requiredOperations);
|
||||
if (!result) {
|
||||
throwPermissionDenied();
|
||||
}
|
||||
}
|
||||
|
@ -487,7 +528,9 @@ module.exports = {
|
|||
rebuildPermissions,
|
||||
removeDefaultShares,
|
||||
enforceEntityPermission,
|
||||
enforceEntityPermissionTx,
|
||||
enforceTypePermission,
|
||||
enforceTypePermissionTx,
|
||||
checkEntityPermission,
|
||||
checkTypePermission,
|
||||
enforceGlobalPermission,
|
||||
|
|
|
@ -123,14 +123,14 @@ async function listDTAjax(context, params) {
|
|||
);
|
||||
}
|
||||
|
||||
async function _validateAndPreprocess(tx, user, isCreate, isOwnAccount) {
|
||||
enforce(await tools.validateEmail(user.email) === 0, 'Invalid email');
|
||||
async function _validateAndPreprocess(tx, entity, isCreate, isOwnAccount) {
|
||||
enforce(await tools.validateEmail(entity.email) === 0, 'Invalid email');
|
||||
|
||||
await namespaceHelpers.validateEntity(tx, user);
|
||||
await namespaceHelpers.validateEntity(tx, entity);
|
||||
|
||||
const otherUserWithSameEmailQuery = tx('users').where('email', user.email);
|
||||
if (user.id) {
|
||||
otherUserWithSameEmailQuery.andWhereNot('id', user.id);
|
||||
const otherUserWithSameEmailQuery = tx('users').where('email', entity.email);
|
||||
if (entity.id) {
|
||||
otherUserWithSameEmailQuery.andWhereNot('id', entity.id);
|
||||
}
|
||||
|
||||
if (await otherUserWithSameEmailQuery.first()) {
|
||||
|
@ -139,9 +139,9 @@ async function _validateAndPreprocess(tx, user, isCreate, isOwnAccount) {
|
|||
|
||||
|
||||
if (!isOwnAccount) {
|
||||
const otherUserWithSameUsernameQuery = tx('users').where('username', user.username);
|
||||
if (user.id) {
|
||||
otherUserWithSameUsernameQuery.andWhereNot('id', user.id);
|
||||
const otherUserWithSameUsernameQuery = tx('users').where('username', entity.username);
|
||||
if (entity.id) {
|
||||
otherUserWithSameUsernameQuery.andWhereNot('id', entity.id);
|
||||
}
|
||||
|
||||
if (await otherUserWithSameUsernameQuery.first()) {
|
||||
|
@ -149,30 +149,28 @@ async function _validateAndPreprocess(tx, user, isCreate, isOwnAccount) {
|
|||
}
|
||||
}
|
||||
|
||||
enforce(user.role in config.roles.global, 'Unknown role');
|
||||
enforce(entity.role in config.roles.global, 'Unknown role');
|
||||
|
||||
enforce(!isCreate || user.password.length > 0, 'Password not set');
|
||||
enforce(!isCreate || entity.password.length > 0, 'Password not set');
|
||||
|
||||
if (user.password) {
|
||||
const passwordValidatorResults = passwordValidator.test(user.password);
|
||||
if (entity.password) {
|
||||
const passwordValidatorResults = passwordValidator.test(entity.password);
|
||||
if (passwordValidatorResults.errors.length > 0) {
|
||||
// This is not an interoperable error because this is not supposed to happen unless the client is tampered with.
|
||||
throw new Error('Invalid password');
|
||||
}
|
||||
|
||||
user.password = await bcryptHash(user.password, null, null);
|
||||
entity.password = await bcryptHash(entity.password, null, null);
|
||||
} else {
|
||||
delete user.password;
|
||||
delete entity.password;
|
||||
}
|
||||
}
|
||||
|
||||
async function create(context, user) {
|
||||
if (context) { // Is also called internally from ldap handling in passport
|
||||
await shares.enforceEntityPermission(context, 'namespace', user.namespace, 'manageUsers');
|
||||
}
|
||||
|
||||
let id;
|
||||
await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', user.namespace, 'manageUsers');
|
||||
|
||||
if (passport.isAuthMethodLocal) {
|
||||
await _validateAndPreprocess(tx, user, true);
|
||||
|
||||
|
@ -208,8 +206,8 @@ async function updateWithConsistencyCheck(context, user, isOwnAccount) {
|
|||
}
|
||||
|
||||
if (!isOwnAccount) {
|
||||
await shares.enforceEntityPermission(context, 'namespace', user.namespace, 'manageUsers');
|
||||
await shares.enforceEntityPermission(context, 'namespace', existing.namespace, 'manageUsers');
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', user.namespace, 'manageUsers');
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', existing.namespace, 'manageUsers');
|
||||
}
|
||||
|
||||
if (passport.isAuthMethodLocal) {
|
||||
|
@ -251,7 +249,7 @@ async function remove(context, userId) {
|
|||
shares.throwPermissionDenied();
|
||||
}
|
||||
|
||||
await shares.enforceEntityPermission(context, 'namespace', existing.namespace, 'manageUsers');
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', existing.namespace, 'manageUsers');
|
||||
|
||||
await tx('users').where('id', userId).del();
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue