Computation of permissions seems to somehow work.
This commit is contained in:
parent
e7bdfb7745
commit
5df444f641
12 changed files with 286 additions and 133 deletions
|
@ -210,7 +210,8 @@ class SectionContent extends Component {
|
||||||
|
|
||||||
errorHandler(error) {
|
errorHandler(error) {
|
||||||
if (error instanceof interoperableErrors.NotLoggedInError) {
|
if (error instanceof interoperableErrors.NotLoggedInError) {
|
||||||
this.ensureAuthenticated();
|
/* FIXME, once we turn Mailtrain to single-page application, this should become navigateTo */
|
||||||
|
window.location = '/account/login?next=' + encodeURIComponent(this.props.root);
|
||||||
} else if (error.response && error.response.data && error.response.data.message) {
|
} else if (error.response && error.response.data && error.response.data.message) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
this.navigateToWithFlashMessage(this.props.root, 'danger', error.response.data.message);
|
this.navigateToWithFlashMessage(this.props.root, 'danger', error.response.data.message);
|
||||||
|
|
|
@ -20,6 +20,10 @@ export default class List extends Component {
|
||||||
{
|
{
|
||||||
label: 'Edit',
|
label: 'Edit',
|
||||||
link: '/namespaces/edit/' + key
|
link: '/namespaces/edit/' + key
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Share',
|
||||||
|
link: '/namespaces/share/' + key
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import i18n from '../lib/i18n';
|
||||||
import { Section } from '../lib/page';
|
import { Section } from '../lib/page';
|
||||||
import CUD from './CUD';
|
import CUD from './CUD';
|
||||||
import List from './List';
|
import List from './List';
|
||||||
|
import Share from '../shares/Share';
|
||||||
|
|
||||||
const getStructure = t => ({
|
const getStructure = t => ({
|
||||||
'': {
|
'': {
|
||||||
|
@ -27,6 +28,11 @@ const getStructure = t => ({
|
||||||
create : {
|
create : {
|
||||||
title: t('Create Namespace'),
|
title: t('Create Namespace'),
|
||||||
render: props => (<CUD {...props} />)
|
render: props => (<CUD {...props} />)
|
||||||
|
},
|
||||||
|
share: {
|
||||||
|
title: t('Share Namespace'),
|
||||||
|
params: [':id'],
|
||||||
|
render: props => (<Share title={entity => t('Share Namespace "{{name}}"', {name: entity.name})} getUrl={id => `/rest/namespaces/${id}`} entityTypeId="namespace" {...props} />)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,10 @@ export default class List extends Component {
|
||||||
{
|
{
|
||||||
label: <span className="glyphicon glyphicon-wrench" aria-hidden="true" title="Edit"></span>,
|
label: <span className="glyphicon glyphicon-wrench" aria-hidden="true" title="Edit"></span>,
|
||||||
link: `/reports/edit/${id}`
|
link: `/reports/edit/${id}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: <span className="glyphicon glyphicon-share" aria-hidden="true" title="Share"></span>,
|
||||||
|
link: `/reports/share/${id}`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,6 +47,11 @@ const getStructure = t => {
|
||||||
params: [':id' ],
|
params: [':id' ],
|
||||||
render: props => (<ReportsOutput {...props} />)
|
render: props => (<ReportsOutput {...props} />)
|
||||||
},
|
},
|
||||||
|
share: {
|
||||||
|
title: t('Share Report'),
|
||||||
|
params: [':id'],
|
||||||
|
render: props => (<Share title={entity => t('Share Report "{{name}}"', {name: entity.name})} getUrl={id => `/rest/reports/${id}`} entityTypeId="report" {...props} />)
|
||||||
|
},
|
||||||
'templates': {
|
'templates': {
|
||||||
title: t('Templates'),
|
title: t('Templates'),
|
||||||
link: '/reports/templates',
|
link: '/reports/templates',
|
||||||
|
|
|
@ -193,16 +193,23 @@ name="Master"
|
||||||
description="All permissions"
|
description="All permissions"
|
||||||
permissions=["view", "edit", "delete"]
|
permissions=["view", "edit", "delete"]
|
||||||
|
|
||||||
#[roles.list.master]
|
[roles.report.master]
|
||||||
#name="Master"
|
name="Master"
|
||||||
#description="All permissions"
|
description="All permissions"
|
||||||
#permissions=["view"]
|
permissions=["view", "edit", "delete"]
|
||||||
#
|
|
||||||
#[roles.namespace.master]
|
[roles.list.master]
|
||||||
#name="Master"
|
name="Master"
|
||||||
#description="All permissions"
|
description="All permissions"
|
||||||
#permissions=["view", "edit", "create", "delete", "create list"]
|
permissions=["view"]
|
||||||
#
|
|
||||||
#[roles.namespace.master.childperms]
|
[roles.namespace.master]
|
||||||
#list=["view"]
|
name="Master"
|
||||||
#namespace=["view", "edit", "create", "delete", "create list"]
|
description="All permissions"
|
||||||
|
permissions=["view", "edit", "create", "delete", "create list"]
|
||||||
|
|
||||||
|
[roles.namespace.master.childperms]
|
||||||
|
reportTemplate=["view", "edit", "delete"]
|
||||||
|
report=["view", "edit", "delete"]
|
||||||
|
list=["view"]
|
||||||
|
namespace=["view", "edit", "create", "delete", "create list"]
|
||||||
|
|
|
@ -4,6 +4,7 @@ const knex = require('../lib/knex');
|
||||||
const hasher = require('node-object-hash')();
|
const hasher = require('node-object-hash')();
|
||||||
const { enforce, filterObject } = require('../lib/helpers');
|
const { enforce, filterObject } = require('../lib/helpers');
|
||||||
const interoperableErrors = require('../shared/interoperable-errors');
|
const interoperableErrors = require('../shared/interoperable-errors');
|
||||||
|
const shares = require('./shares');
|
||||||
|
|
||||||
const allowedKeys = new Set(['name', 'description', 'namespace']);
|
const allowedKeys = new Set(['name', 'description', 'namespace']);
|
||||||
|
|
||||||
|
@ -34,6 +35,9 @@ async function create(entity) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We don't have to rebuild all entity types, because no entity can be a child of the namespace at this moment.
|
||||||
|
await shares.rebuildPermissions(tx, { entityTypeId: 'namespace', entityId: id });
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -66,6 +70,8 @@ async function updateWithConsistencyCheck(entity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
await tx('namespaces').where('id', entity.id).update(filterObject(entity, allowedKeys));
|
await tx('namespaces').where('id', entity.id).update(filterObject(entity, allowedKeys));
|
||||||
|
|
||||||
|
await shares.rebuildPermissions(tx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ const { enforce, filterObject } = require('../lib/helpers');
|
||||||
const dtHelpers = require('../lib/dt-helpers');
|
const dtHelpers = require('../lib/dt-helpers');
|
||||||
const interoperableErrors = require('../shared/interoperable-errors');
|
const interoperableErrors = require('../shared/interoperable-errors');
|
||||||
const namespaceHelpers = require('../lib/namespace-helpers');
|
const namespaceHelpers = require('../lib/namespace-helpers');
|
||||||
|
const shares = require('./shares');
|
||||||
|
|
||||||
const allowedKeys = new Set(['name', 'description', 'mime_type', 'user_fields', 'js', 'hbs', 'namespace']);
|
const allowedKeys = new Set(['name', 'description', 'mime_type', 'user_fields', 'js', 'hbs', 'namespace']);
|
||||||
|
|
||||||
|
@ -33,7 +34,11 @@ async function listDTAjax(params) {
|
||||||
async function create(entity) {
|
async function create(entity) {
|
||||||
await knex.transaction(async tx => {
|
await knex.transaction(async tx => {
|
||||||
await namespaceHelpers.validateEntity(tx, entity);
|
await namespaceHelpers.validateEntity(tx, entity);
|
||||||
|
|
||||||
const id = await tx('report_templates').insert(filterObject(entity, allowedKeys));
|
const id = await tx('report_templates').insert(filterObject(entity, allowedKeys));
|
||||||
|
|
||||||
|
await shares.rebuildPermissions(tx, { entityTypeId: 'reportTemplate', entityId: id });
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -53,6 +58,8 @@ async function updateWithConsistencyCheck(entity) {
|
||||||
await namespaceHelpers.validateEntity(tx, entity);
|
await namespaceHelpers.validateEntity(tx, entity);
|
||||||
|
|
||||||
await tx('report_templates').where('id', entity.id).update(filterObject(entity, allowedKeys));
|
await tx('report_templates').where('id', entity.id).update(filterObject(entity, allowedKeys));
|
||||||
|
|
||||||
|
await shares.rebuildPermissions(tx, { entityTypeId: 'reportTemplate', entityId: entity.id });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ const dtHelpers = require('../lib/dt-helpers');
|
||||||
const interoperableErrors = require('../shared/interoperable-errors');
|
const interoperableErrors = require('../shared/interoperable-errors');
|
||||||
const fields = require('./fields');
|
const fields = require('./fields');
|
||||||
const namespaceHelpers = require('../lib/namespace-helpers');
|
const namespaceHelpers = require('../lib/namespace-helpers');
|
||||||
|
const shares = require('./shares');
|
||||||
|
|
||||||
const ReportState = require('../shared/reports').ReportState;
|
const ReportState = require('../shared/reports').ReportState;
|
||||||
|
|
||||||
|
@ -56,6 +57,8 @@ async function create(entity) {
|
||||||
entity.params = JSON.stringify(entity.params);
|
entity.params = JSON.stringify(entity.params);
|
||||||
|
|
||||||
id = await tx('reports').insert(filterObject(entity, allowedKeys));
|
id = await tx('reports').insert(filterObject(entity, allowedKeys));
|
||||||
|
|
||||||
|
await shares.rebuildPermissions(tx, { entityTypeId: 'report', entityId: id });
|
||||||
});
|
});
|
||||||
|
|
||||||
const reportProcessor = require('../lib/report-processor');
|
const reportProcessor = require('../lib/report-processor');
|
||||||
|
@ -89,6 +92,8 @@ async function updateWithConsistencyCheck(entity) {
|
||||||
filteredUpdates.state = ReportState.SCHEDULED;
|
filteredUpdates.state = ReportState.SCHEDULED;
|
||||||
|
|
||||||
await tx('reports').where('id', entity.id).update(filteredUpdates);
|
await tx('reports').where('id', entity.id).update(filteredUpdates);
|
||||||
|
|
||||||
|
await shares.rebuildPermissions(tx, { entityTypeId: 'report', entityId: entity.id });
|
||||||
});
|
});
|
||||||
|
|
||||||
// This require is here to avoid cyclic dependency
|
// This require is here to avoid cyclic dependency
|
||||||
|
|
200
models/shares.js
200
models/shares.js
|
@ -7,11 +7,21 @@ const dtHelpers = require('../lib/dt-helpers');
|
||||||
const interoperableErrors = require('../shared/interoperable-errors');
|
const interoperableErrors = require('../shared/interoperable-errors');
|
||||||
|
|
||||||
const entityTypes = {
|
const entityTypes = {
|
||||||
reportTemplate: {
|
namespace: {
|
||||||
entitiesTable: 'report_templates',
|
entitiesTable: 'namespaces',
|
||||||
sharesTable: 'shares_report_template',
|
sharesTable: 'shares_namespace',
|
||||||
permissionsTable: 'permissions_report_template'
|
permissionsTable: 'permissions_namespace'
|
||||||
}
|
},
|
||||||
|
report: {
|
||||||
|
entitiesTable: 'reports',
|
||||||
|
sharesTable: 'shares_report',
|
||||||
|
permissionsTable: 'permissions_report'
|
||||||
|
},
|
||||||
|
reportTemplate: {
|
||||||
|
entitiesTable: 'report_templates',
|
||||||
|
sharesTable: 'shares_report_template',
|
||||||
|
permissionsTable: 'permissions_report_template'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function getEntityType(entityTypeId) {
|
function getEntityType(entityTypeId) {
|
||||||
|
@ -26,14 +36,14 @@ function getEntityType(entityTypeId) {
|
||||||
|
|
||||||
async function listDTAjax(entityTypeId, entityId, params) {
|
async function listDTAjax(entityTypeId, entityId, params) {
|
||||||
const entityType = getEntityType(entityTypeId);
|
const entityType = getEntityType(entityTypeId);
|
||||||
return await dtHelpers.ajaxList(params, tx => tx(entityType.sharesTable).innerJoin('users', entityType.sharesTable + '.user', 'users.id'), [entityType.sharesTable + '.id', 'users.username', 'users.name', entityType.sharesTable + '.role', 'users.id']);
|
return await dtHelpers.ajaxList(params, tx => tx(entityType.sharesTable).innerJoin('users', entityType.sharesTable + '.user', 'users.id').where(`${entityType.sharesTable}.entity`, entityId), [entityType.sharesTable + '.id', 'users.username', 'users.name', entityType.sharesTable + '.role', 'users.id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function listUnassignedUsersDTAjax(entityTypeId, entityId, params) {
|
async function listUnassignedUsersDTAjax(entityTypeId, entityId, params) {
|
||||||
const entityType = getEntityType(entityTypeId);
|
const entityType = getEntityType(entityTypeId);
|
||||||
return await dtHelpers.ajaxList(
|
return await dtHelpers.ajaxList(
|
||||||
params,
|
params,
|
||||||
tx => tx('users').whereNotExists(function() { return this.select('*').from(entityType.sharesTable).whereRaw(`users.id = ${entityType.sharesTable}.user`); }),
|
tx => tx('users').whereNotExists(function() { return this.select('*').from(entityType.sharesTable).whereRaw(`users.id = ${entityType.sharesTable}.user`).andWhere(`${entityType.sharesTable}.entity`, entityId); }),
|
||||||
['users.id', 'users.username', 'users.name']);
|
['users.id', 'users.username', 'users.name']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,29 +71,175 @@ async function assign(entityTypeId, entityId, userId, role) {
|
||||||
}
|
}
|
||||||
|
|
||||||
await tx(entityType.permissionsTable).where({user: userId, entity: entityId}).del();
|
await tx(entityType.permissionsTable).where({user: userId, entity: entityId}).del();
|
||||||
if (role) {
|
if (entityTypeId === 'namespace') {
|
||||||
const permissions = config.roles[entityTypeId][role].permissions;
|
await rebuildPermissions(tx, {userId});
|
||||||
const data = permissions.map(operation => ({user: userId, entity: entityId, operation}));
|
} else if (role) {
|
||||||
await tx(entityType.permissionsTable).insert(data);
|
await rebuildPermissions(tx, { entityTypeId, entityId, userId });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function rebuildPermissions() {
|
async function rebuildPermissions(tx, restriction) {
|
||||||
await knex.transaction(async tx => {
|
restriction = restriction || {};
|
||||||
for (const entityTypeId in entityTypes) {
|
|
||||||
const entityType = entityTypes[entityTypeId];
|
|
||||||
|
|
||||||
await tx(entityType.permissionsTable).del();
|
let restrictedEntityTypes;
|
||||||
|
if (restriction.entityTypeId) {
|
||||||
|
restrictedEntityTypes = {
|
||||||
|
[restriction.entityTypeId]: entityTypes[restriction.entityTypeId]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
restrictedEntityTypes = entityTypes;
|
||||||
|
}
|
||||||
|
|
||||||
const shares = await tx(entityType.sharesTable).select(['entity', 'user', 'role']);
|
|
||||||
for (const share in shares) {
|
const namespaces = await tx('namespaces').select(['id', 'namespace']);
|
||||||
const permissions = config.roles[entityTypeId][share.role].permissions;
|
|
||||||
const data = permissions.map(operation => ({user: share.user, entity: share.entity, operation}));
|
// nsMap is a map of namespaces - each of the following shape:
|
||||||
await tx(entityType.permissionsTable).insert(data);
|
// .id - id of the namespace
|
||||||
|
// .namespace - id of the parent or null if no parent
|
||||||
|
// .userPermissions - Map userId -> [entityTypeId] -> array of permissions
|
||||||
|
// .transitiveUserPermissions - the same as above, but taking into account transitive permission obtained from namespace parents
|
||||||
|
const nsMap = new Map();
|
||||||
|
for (const namespace of namespaces) {
|
||||||
|
namespace.userPermissions = new Map();
|
||||||
|
nsMap.set(namespace.id, namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This populates .userPermissions
|
||||||
|
const nsSharesQuery = tx(entityTypes.namespace.sharesTable).select(['entity', 'user', 'role']);
|
||||||
|
if (restriction.userId) {
|
||||||
|
nsSharesQuery.andWhere('user', restriction.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nsShares = await nsSharesQuery;
|
||||||
|
for (const nsShare of nsShares) {
|
||||||
|
const ns = nsMap.get(nsShare.entity);
|
||||||
|
|
||||||
|
const userPerms = {};
|
||||||
|
ns.userPermissions.set(nsShare.user, userPerms);
|
||||||
|
|
||||||
|
for (const entityTypeId in restrictedEntityTypes) {
|
||||||
|
if (config.roles.namespace[nsShare.role] &&
|
||||||
|
config.roles.namespace[nsShare.role].childperms &&
|
||||||
|
config.roles.namespace[nsShare.role].childperms[entityTypeId]) {
|
||||||
|
|
||||||
|
userPerms[entityTypeId] = new Set(config.roles.namespace[nsShare.role].childperms[entityTypeId]);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
userPerms[entityTypeId] = new Set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// This computes .transitiveUserPermissions
|
||||||
|
for (const ns of nsMap.values()) {
|
||||||
|
ns.transitiveUserPermissions = new Map();
|
||||||
|
|
||||||
|
for (const userPermsPair of ns.userPermissions) {
|
||||||
|
const userPerms = {};
|
||||||
|
ns.transitiveUserPermissions.set(userPermsPair[0], userPerms);
|
||||||
|
|
||||||
|
for (const entityTypeId in restrictedEntityTypes) {
|
||||||
|
userPerms[entityTypeId] = new Set(userPermsPair[1][entityTypeId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let parentId = ns.namespace;
|
||||||
|
while (parentId) {
|
||||||
|
const parent = nsMap.get(parentId);
|
||||||
|
|
||||||
|
for (const userPermsPair of parent.userPermissions) {
|
||||||
|
const user = userPermsPair[0];
|
||||||
|
|
||||||
|
if (ns.transitiveUserPermissions.has(user)) {
|
||||||
|
const userPerms = ns.transitiveUserPermissions.get(user);
|
||||||
|
|
||||||
|
for (const entityTypeId in restrictedEntityTypes) {
|
||||||
|
for (const perm of userPermsPair[1][entityTypeId]) {
|
||||||
|
userPerms[entityTypeId].add(perm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const userPerms = {}
|
||||||
|
ns.transitiveUserPermissions.set(user, userPerms);
|
||||||
|
|
||||||
|
for (const entityTypeId in restrictedEntityTypes) {
|
||||||
|
userPerms[entityTypeId] = new Set(userPermsPair[1][entityTypeId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parentId = parent.namespace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This reads direct shares from DB, joins it with the permissions from namespaces and stores the permissions into DB
|
||||||
|
for (const entityTypeId in restrictedEntityTypes) {
|
||||||
|
const entityType = restrictedEntityTypes[entityTypeId];
|
||||||
|
|
||||||
|
const expungeQuery = tx(entityType.permissionsTable).del();
|
||||||
|
if (restriction.entityId) {
|
||||||
|
expungeQuery.andWhere('entity', restriction.entityId);
|
||||||
|
}
|
||||||
|
if (restriction.userId) {
|
||||||
|
expungeQuery.andWhere('user', restriction.userId);
|
||||||
|
}
|
||||||
|
await expungeQuery;
|
||||||
|
|
||||||
|
const entitiesQuery = tx(entityType.entitiesTable).select(['id', 'namespace']);
|
||||||
|
if (restriction.entityId) {
|
||||||
|
entitiesQuery.andWhere('id', restriction.entityId);
|
||||||
|
}
|
||||||
|
const entities = await entitiesQuery;
|
||||||
|
|
||||||
|
for (const entity of entities) {
|
||||||
|
const permsPerUser = new Map();
|
||||||
|
|
||||||
|
if (entity.namespace) { // The root namespace has not parent namespace, thus the test
|
||||||
|
const transitiveUserPermissions = nsMap.get(entity.namespace).transitiveUserPermissions;
|
||||||
|
for (const transitivePermsPair of transitiveUserPermissions.entries()) {
|
||||||
|
permsPerUser.set(transitivePermsPair[0], [...transitivePermsPair[1][entityTypeId]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const directSharesQuery = tx(entityType.sharesTable).select(['user', 'role']).where('entity', entity.id);
|
||||||
|
if (restriction.userId) {
|
||||||
|
directSharesQuery.andWhere('user', restriction.userId);
|
||||||
|
}
|
||||||
|
const directShares = await directSharesQuery;
|
||||||
|
|
||||||
|
for (const share of directShares) {
|
||||||
|
let userPerms;
|
||||||
|
if (permsPerUser.has(share.user)) {
|
||||||
|
userPerms = permsPerUser.get(share.user);
|
||||||
|
} else {
|
||||||
|
userPerms = new Set();
|
||||||
|
permsPerUser.set(share.user, userPerms);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.roles[entityTypeId][share.role] &&
|
||||||
|
config.roles[entityTypeId][share.role].permissions) {
|
||||||
|
|
||||||
|
for (const perm of config.roles[entityTypeId][share.role].permissions) {
|
||||||
|
userPerms.add(perm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const userPermsPair of permsPerUser.entries()) {
|
||||||
|
const data = [];
|
||||||
|
|
||||||
|
for (const operation of userPermsPair[1]) {
|
||||||
|
data.push({user: userPermsPair[0], entity: entity.id, operation});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length > 0) {
|
||||||
|
await tx(entityType.permissionsTable).insert(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -1,58 +1,33 @@
|
||||||
exports.up = function(knex, Promise) {
|
exports.up = function(knex, Promise) {
|
||||||
return knex.schema.createTable('namespaces', table => {
|
const entityTypesAddNamespace = ['list', 'report', 'report_template', 'user'];
|
||||||
|
let schema = knex.schema;
|
||||||
|
|
||||||
|
schema = schema.createTable('namespaces', table => {
|
||||||
table.increments('id').primary();
|
table.increments('id').primary();
|
||||||
table.string('name');
|
table.string('name');
|
||||||
table.text('description');
|
table.text('description');
|
||||||
table.integer('namespace').unsigned().references('namespaces.id').onDelete('CASCADE');
|
table.integer('namespace').unsigned().references('namespaces.id').onDelete('CASCADE');
|
||||||
})
|
})
|
||||||
|
|
||||||
.then(() => knex('namespaces').insert({
|
.then(() => knex('namespaces').insert({
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'Global',
|
name: 'Global',
|
||||||
description: 'Global namespace'
|
description: 'Global namespace'
|
||||||
}))
|
}));
|
||||||
|
|
||||||
.then(() => knex.schema.table('users', table => {
|
for (const entityType of entityTypesAddNamespace) {
|
||||||
table.integer('namespace').unsigned().notNullable();
|
schema = schema
|
||||||
}))
|
.then(() => knex.schema.table(`${entityType}s`, table => {
|
||||||
.then(() => knex('users').update({
|
table.integer('namespace').unsigned().notNullable();
|
||||||
namespace: 1
|
}))
|
||||||
}))
|
.then(() => knex(`${entityType}s`).update({
|
||||||
.then(() => knex.schema.table('users', table => {
|
namespace: 1
|
||||||
table.foreign('namespace').references('namespaces.id').onDelete('CASCADE');
|
}))
|
||||||
}))
|
.then(() => knex.schema.table(`${entityType}s`, table => {
|
||||||
|
table.foreign('namespace').references('namespaces.id').onDelete('CASCADE');
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
.then(() => knex.schema.table('lists', table => {
|
return schema;
|
||||||
table.integer('namespace').unsigned().notNullable();
|
|
||||||
}))
|
|
||||||
.then(() => knex('lists').update({
|
|
||||||
namespace: 1
|
|
||||||
}))
|
|
||||||
.then(() => knex.schema.table('lists', table => {
|
|
||||||
table.foreign('namespace').references('namespaces.id').onDelete('CASCADE');
|
|
||||||
}))
|
|
||||||
|
|
||||||
.then(() => knex.schema.table('report_templates', table => {
|
|
||||||
table.integer('namespace').unsigned().notNullable();
|
|
||||||
}))
|
|
||||||
.then(() => knex('report_templates').update({
|
|
||||||
namespace: 1
|
|
||||||
}))
|
|
||||||
.then(() => knex.schema.table('report_templates', table => {
|
|
||||||
table.foreign('namespace').references('namespaces.id').onDelete('CASCADE');
|
|
||||||
}))
|
|
||||||
|
|
||||||
.then(() => knex.schema.table('reports', table => {
|
|
||||||
table.integer('namespace').unsigned().notNullable();
|
|
||||||
}))
|
|
||||||
.then(() => knex('reports').update({
|
|
||||||
namespace: 1
|
|
||||||
}))
|
|
||||||
.then(() => knex.schema.table('reports', table => {
|
|
||||||
table.foreign('namespace').references('namespaces.id').onDelete('CASCADE');
|
|
||||||
}))
|
|
||||||
|
|
||||||
;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.down = function(knex, Promise) {
|
exports.down = function(knex, Promise) {
|
||||||
|
|
|
@ -1,67 +1,44 @@
|
||||||
const config = require('../config');
|
const shareableEntityTypes = ['list', 'report', 'report_template', 'namespace'];
|
||||||
|
|
||||||
exports.up = function(knex, Promise) {
|
exports.up = function(knex, Promise) {
|
||||||
return knex.schema.createTable('shares_list', table => {
|
let schema = knex.schema;
|
||||||
table.increments('id').primary();
|
|
||||||
table.integer('entity').unsigned().notNullable().references('lists.id').onDelete('CASCADE');
|
|
||||||
table.integer('user').unsigned().notNullable().references('users.id').onDelete('CASCADE');
|
|
||||||
table.string('role', 64).notNullable();
|
|
||||||
table.unique(['entity', 'user']);
|
|
||||||
})
|
|
||||||
|
|
||||||
.createTable('permissions_list', table => {
|
for (const entityType of shareableEntityTypes) {
|
||||||
table.increments('id').primary();
|
schema = schema
|
||||||
table.integer('entity').unsigned().notNullable().references('lists.id').onDelete('CASCADE');
|
.createTable(`shares_${entityType}`, table => {
|
||||||
table.integer('user').unsigned().notNullable().references('users.id').onDelete('CASCADE');
|
table.increments('id').primary();
|
||||||
table.string('operation', 64).notNullable();
|
table.integer('entity').unsigned().notNullable().references(`${entityType}s.id`).onDelete('CASCADE');
|
||||||
table.unique(['entity', 'user', 'operation']);
|
table.integer('user').unsigned().notNullable().references('users.id').onDelete('CASCADE');
|
||||||
})
|
table.string('role', 64).notNullable();
|
||||||
|
table.unique(['entity', 'user']);
|
||||||
|
})
|
||||||
|
.createTable(`permissions_${entityType}`, table => {
|
||||||
|
table.increments('id').primary();
|
||||||
|
table.integer('entity').unsigned().notNullable().references(`${entityType}s.id`).onDelete('CASCADE');
|
||||||
|
table.integer('user').unsigned().notNullable().references('users.id').onDelete('CASCADE');
|
||||||
|
table.string('operation', 64).notNullable();
|
||||||
|
table.unique(['entity', 'user', 'operation']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
.createTable('shares_report_template', table => {
|
schema = schema.then(() => knex('shares_namespace').insert({
|
||||||
table.increments('id').primary();
|
|
||||||
table.integer('entity').unsigned().notNullable().references('report_templates.id').onDelete('CASCADE');
|
|
||||||
table.integer('user').unsigned().notNullable().references('users.id').onDelete('CASCADE');
|
|
||||||
table.string('role', 64).notNullable();
|
|
||||||
table.unique(['entity', 'user']);
|
|
||||||
})
|
|
||||||
|
|
||||||
.createTable('permissions_report_template', table => {
|
|
||||||
table.increments('id').primary();
|
|
||||||
table.integer('entity').unsigned().notNullable().references('report_templates.id').onDelete('CASCADE');
|
|
||||||
table.integer('user').unsigned().notNullable().references('users.id').onDelete('CASCADE');
|
|
||||||
table.string('operation', 64).notNullable();
|
|
||||||
table.unique(['entity', 'user', 'operation']);
|
|
||||||
})
|
|
||||||
|
|
||||||
.createTable('shares_namespace', table => {
|
|
||||||
table.increments('id').primary();
|
|
||||||
table.integer('entity').unsigned().notNullable().references('namespaces.id').onDelete('CASCADE');
|
|
||||||
table.integer('user').unsigned().notNullable().references('users.id').onDelete('CASCADE');
|
|
||||||
table.string('role', 64).notNullable();
|
|
||||||
table.unique(['entity', 'user']);
|
|
||||||
})
|
|
||||||
|
|
||||||
.createTable('permissions_namespace', table => {
|
|
||||||
table.increments('id').primary();
|
|
||||||
table.integer('entity').unsigned().notNullable().references('namespaces.id').onDelete('CASCADE');
|
|
||||||
table.integer('user').unsigned().notNullable().references('users.id').onDelete('CASCADE');
|
|
||||||
table.string('operation', 64).notNullable();
|
|
||||||
table.unique(['entity', 'user', 'operation']);
|
|
||||||
})
|
|
||||||
|
|
||||||
.then(() => knex('shares_namespace').insert({
|
|
||||||
id: 1,
|
id: 1,
|
||||||
entity: 1,
|
entity: 1,
|
||||||
user: 1,
|
user: 1,
|
||||||
role: 'master'
|
role: 'master'
|
||||||
}))
|
}));
|
||||||
|
|
||||||
;
|
return schema;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.down = function(knex, Promise) {
|
exports.down = function(knex, Promise) {
|
||||||
return knex.schema.dropTable('shares_namespace')
|
let schema = knex.schema;
|
||||||
.dropTable('shares_list')
|
|
||||||
.dropTable('permissions_namespace')
|
for (const entityType of shareableEntityTypes) {
|
||||||
.dropTable('permissions_list');
|
schema = schema
|
||||||
|
.dropTable(`shares_${entityType}`)
|
||||||
|
.dropTable(`permissions_${entityType}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema;
|
||||||
};
|
};
|
Loading…
Reference in a new issue