Harmonization with IVIS
This commit is contained in:
parent
428fb9db7b
commit
397f85dac4
41 changed files with 8587 additions and 10940 deletions
|
@ -13,7 +13,7 @@ const dependencyHelpers = require('../lib/dependency-helpers');
|
|||
const allowedKeys = new Set(['name', 'description', 'namespace']);
|
||||
|
||||
async function listTree(context) {
|
||||
// FIXME - process permissions
|
||||
enforce(!context.user.admin, 'listTree is not supposed to be called by assumed admin');
|
||||
|
||||
const entityType = entitySettings.getEntityType('namespace');
|
||||
|
||||
|
@ -64,6 +64,7 @@ async function listTree(context) {
|
|||
entry.title = row.name;
|
||||
entry.description = row.description;
|
||||
entry.permissions = row.permissions ? row.permissions.split(';') : [];
|
||||
entry.permissions = shares.filterPermissionsByRestrictedAccessHandler(context, 'namespace', row.id, entry.permissions, 'namespaces.listTree')
|
||||
}
|
||||
|
||||
// Prune out the inaccessible namespaces
|
||||
|
@ -117,19 +118,66 @@ async function getById(context, id) {
|
|||
});
|
||||
}
|
||||
|
||||
async function create(context, entity) {
|
||||
async function getChildrenTx(tx, context, id) {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', id, 'view');
|
||||
|
||||
const entityType = entitySettings.getEntityType('namespace');
|
||||
|
||||
const extraKeys = em.get('models.namespaces.extraKeys', []);
|
||||
|
||||
let children;
|
||||
if (context.user.admin) {
|
||||
children = await knex('namespaces')
|
||||
.where('namespaces.namespace', id)
|
||||
.select([
|
||||
'namespaces.id', 'namespaces.name', 'namespaces.description', 'namespaces.namespace',
|
||||
...extraKeys.map(key => 'namespaces.' + key)
|
||||
]);
|
||||
|
||||
} else {
|
||||
const rows = await knex('namespaces')
|
||||
.leftJoin(entityType.permissionsTable, {
|
||||
[entityType.permissionsTable + '.entity']: 'namespaces.id',
|
||||
[entityType.permissionsTable + '.user']: context.user.id
|
||||
})
|
||||
.where('namespaces.namespace', id)
|
||||
.groupBy('namespaces.id')
|
||||
.select([
|
||||
'namespaces.id', 'namespaces.name', 'namespaces.description', 'namespaces.namespace',
|
||||
...extraKeys.map(key => 'namespaces.' + key),
|
||||
knex.raw(`GROUP_CONCAT(${entityType.permissionsTable + '.operation'} SEPARATOR \';\') as permissions`)
|
||||
]);
|
||||
|
||||
children = [];
|
||||
for (const row of rows) {
|
||||
row.permissions = row.permissions ? row.permissions.split(';') : [];
|
||||
row.permissions = shares.filterPermissionsByRestrictedAccessHandler(context, 'namespace', row.id, row.permissions, 'namespaces.getChildrenTx');
|
||||
if (row.permissions.includes('view')) {
|
||||
children.push(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
async function createTx(tx, context, entity) {
|
||||
enforce(entity.namespace, 'Parent namespace must be set');
|
||||
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createNamespace');
|
||||
|
||||
const ids = await tx('namespaces').insert(filterObject(entity, allowedKeys));
|
||||
const id = ids[0];
|
||||
|
||||
// We don't have to rebuild all entity types, because no entity can be a child of the namespace at this moment.
|
||||
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'namespace', entityId: id });
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
async function create(context, entity) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'namespace', entity.namespace, 'createNamespace');
|
||||
|
||||
const ids = await tx('namespaces').insert(filterObject(entity, allowedKeys));
|
||||
const id = ids[0];
|
||||
|
||||
// We don't have to rebuild all entity types, because no entity can be a child of the namespace at this moment.
|
||||
await shares.rebuildPermissionsTx(tx, { entityTypeId: 'namespace', entityId: id });
|
||||
|
||||
return id;
|
||||
return await createTx(tx, context, entity);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -187,6 +235,8 @@ async function remove(context, id) {
|
|||
module.exports.hash = hash;
|
||||
module.exports.listTree = listTree;
|
||||
module.exports.getById = getById;
|
||||
module.exports.getChildrenTx = getChildrenTx;
|
||||
module.exports.create = create;
|
||||
module.exports.createTx = createTx;
|
||||
module.exports.updateWithConsistencyCheck = updateWithConsistencyCheck;
|
||||
module.exports.remove = remove;
|
||||
|
|
|
@ -332,8 +332,6 @@ async function rebuildPermissionsTx(tx, restriction) {
|
|||
}
|
||||
const entities = await entitiesQuery;
|
||||
|
||||
// TODO - process restriction.parentId
|
||||
|
||||
const parentEntities = new Map();
|
||||
let nonChildEntities;
|
||||
if (entityType.dependentPermissions) {
|
||||
|
@ -541,30 +539,7 @@ async function _checkPermissionTx(tx, context, entityTypeId, entityId, requiredO
|
|||
requiredOperations = [ requiredOperations ];
|
||||
}
|
||||
|
||||
if (context.user.restrictedAccessHandler) {
|
||||
const originalRequiredOperations = requiredOperations;
|
||||
if (context.user.restrictedAccessHandler.permissions) {
|
||||
const entityPerms = context.user.restrictedAccessHandler.permissions[entityTypeId];
|
||||
|
||||
if (!entityPerms) {
|
||||
requiredOperations = [];
|
||||
} else if (entityPerms === true) {
|
||||
// no change to require operations
|
||||
} else if (entityPerms instanceof Set) {
|
||||
requiredOperations = requiredOperations.filter(perm => entityPerms.has(perm));
|
||||
} else {
|
||||
const allowedPerms = entityPerms[entityId];
|
||||
if (allowedPerms) {
|
||||
requiredOperations = requiredOperations.filter(perm => allowedPerms.has(perm));
|
||||
} else {
|
||||
requiredOperations = [];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
requiredOperations = [];
|
||||
}
|
||||
log.verbose('check permissions with restrictedAccessHandler -- entityTypeId: ' + entityTypeId + ' entityId: ' + entityId + ' requiredOperations: [' + originalRequiredOperations + '] -> [' + requiredOperations + ']');
|
||||
}
|
||||
requiredOperations = filterPermissionsByRestrictedAccessHandler(context, entityTypeId, entityId, requiredOperations, 'checkPermissions');
|
||||
|
||||
if (requiredOperations.length === 0) {
|
||||
return false;
|
||||
|
@ -686,9 +661,59 @@ async function getPermissionsTx(tx, context, entityTypeId, entityId) {
|
|||
.where('entity', entityId)
|
||||
.where('user', context.user.id);
|
||||
|
||||
return rows.map(x => x.operation);
|
||||
const operations = rows.map(x => x.operation);
|
||||
return filterPermissionsByRestrictedAccessHandler(context, entityTypeId, entityId, operations, 'getPermissions');
|
||||
}
|
||||
|
||||
// If entityId is null, it means that we require that restrictedAccessHandler does not differentiate based on entityId. This is used in ajaxListWithPermissionsTx.
|
||||
function filterPermissionsByRestrictedAccessHandler(context, entityTypeId, entityId, permissions, operationMsg) {
|
||||
if (context.user.restrictedAccessHandler) {
|
||||
const originalOperations = permissions;
|
||||
if (context.user.restrictedAccessHandler.permissions) {
|
||||
const entityPerms = context.user.restrictedAccessHandler.permissions[entityTypeId];
|
||||
|
||||
if (!entityPerms) {
|
||||
permissions = [];
|
||||
} else if (entityPerms === true) {
|
||||
// no change to operations
|
||||
} else if (entityPerms instanceof Set) {
|
||||
permissions = permissions.filter(perm => entityPerms.has(perm));
|
||||
} else {
|
||||
if (entityId) {
|
||||
const allowedPerms = entityPerms[entityId];
|
||||
if (allowedPerms) {
|
||||
permissions = permissions.filter(perm => allowedPerms.has(perm));
|
||||
} else {
|
||||
const allowedPerms = entityPerms['default'];
|
||||
if (allowedPerms) {
|
||||
permissions = permissions.filter(perm => allowedPerms.has(perm));
|
||||
} else {
|
||||
permissions = [];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const allowedPerms = entityPerms['default'];
|
||||
if (allowedPerms) {
|
||||
permissions = permissions.filter(perm => allowedPerms.has(perm));
|
||||
} else {
|
||||
permissions = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
permissions = [];
|
||||
}
|
||||
log.verbose(operationMsg + ' with restrictedAccessHandler -- entityTypeId: ' + entityTypeId + ' entityId: ' + entityId + ' operations: [' + originalOperations + '] -> [' + permissions + ']');
|
||||
}
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
function isAccessibleByRestrictedAccessHandler(context, entityTypeId, entityId, permissions, operationMsg) {
|
||||
return filterPermissionsByRestrictedAccessHandler(context, entityTypeId, entityId, permissions, operationMsg).length > 0;
|
||||
}
|
||||
|
||||
|
||||
module.exports.listByEntityDTAjax = listByEntityDTAjax;
|
||||
module.exports.listByUserDTAjax = listByUserDTAjax;
|
||||
module.exports.listUnassignedUsersDTAjax = listUnassignedUsersDTAjax;
|
||||
|
@ -710,3 +735,5 @@ module.exports.throwPermissionDenied = throwPermissionDenied;
|
|||
module.exports.regenerateRoleNamesTable = regenerateRoleNamesTable;
|
||||
module.exports.getGlobalPermissions = getGlobalPermissions;
|
||||
module.exports.getPermissionsTx = getPermissionsTx;
|
||||
module.exports.filterPermissionsByRestrictedAccessHandler = filterPermissionsByRestrictedAccessHandler;
|
||||
module.exports.isAccessibleByRestrictedAccessHandler = isAccessibleByRestrictedAccessHandler;
|
|
@ -36,20 +36,28 @@ function hash(entity) {
|
|||
return hasher.hash(filterObject(entity, hashKeys));
|
||||
}
|
||||
|
||||
async function _getBy(context, key, value, extraColumns = []) {
|
||||
async function _getByTx(tx, context, key, value, extraColumns = []) {
|
||||
const columns = ['id', 'username', 'name', 'email', 'namespace', 'role', ...extraColumns];
|
||||
|
||||
const user = await knex('users').select(columns).where(key, value).first();
|
||||
const user = await tx('users').select(columns).where(key, value).first();
|
||||
|
||||
if (!user) {
|
||||
shares.throwPermissionDenied();
|
||||
}
|
||||
|
||||
// Note that getRestrictedAccessToken relies to this check to see whether a user may impersonate another. If "manageUsers" here were to be changed to something like "viewUsers", then
|
||||
// a corresponding check has to be added to getRestrictedAccessToken
|
||||
await shares.enforceEntityPermission(context, 'namespace', user.namespace, 'manageUsers');
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async function _getBy(context, key, value, extraColumns = []) {
|
||||
return await knex.transaction(async tx => {
|
||||
return await _getByTx(tx, context, key, value, extraColumns);
|
||||
});
|
||||
}
|
||||
|
||||
async function getById(context, id) {
|
||||
return await _getBy(context, 'id', id);
|
||||
}
|
||||
|
@ -131,7 +139,7 @@ async function _validateAndPreprocess(tx, entity, isCreate, isOwnAccount) {
|
|||
|
||||
if (!isOwnAccount) {
|
||||
const otherUserWithSameUsernameQuery = tx('users').where('username', entity.username);
|
||||
if (entity.id) {
|
||||
if (!isCreate) {
|
||||
otherUserWithSameUsernameQuery.andWhereNot('id', entity.id);
|
||||
}
|
||||
|
||||
|
@ -254,9 +262,9 @@ async function getByUsername(username) {
|
|||
return await _getBy(contextHelpers.getAdminContext(), 'username', username);
|
||||
}
|
||||
|
||||
async function getByUsernameIfPasswordMatch(username, password) {
|
||||
async function getByUsernameIfPasswordMatch(context, username, password) {
|
||||
try {
|
||||
const user = await _getBy(contextHelpers.getAdminContext(), 'username', username, ['password']);
|
||||
const user = await _getBy('username', username, ['password']);
|
||||
|
||||
if (!await bcryptCompare(password, user.password)) {
|
||||
throw new interoperableErrors.IncorrectPasswordError();
|
||||
|
@ -405,8 +413,10 @@ async function getByRestrictedAccessToken(token) {
|
|||
|
||||
if (tokenEntry) {
|
||||
const user = await getById(contextHelpers.getAdminContext(), tokenEntry.userId);
|
||||
user.restrictedAccessMethod = tokenEntry.method;
|
||||
user.restrictedAccessHandler = tokenEntry.handler;
|
||||
user.restrictedAccessToken = tokenEntry.token;
|
||||
user.restrictedAccessParams = tokenEntry.params;
|
||||
|
||||
return user;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue