globalstorage
Version:
Global Storage is a Global Distributed Data Warehouse
330 lines (300 loc) • 8.97 kB
JavaScript
'use strict';
const metasync = require('metasync');
const { Uint64 } = require('@metarhia/common');
const { escapeIdentifier } = require('./pg.utils');
const { runIf } = require('./utils');
const permissionFlags = {
read: 0b0001,
insert: 0b0010,
update: 0b0100,
delete: 0b1000,
};
// TODO replace the SQL queries with Cursor usage so that it is not pinned to
// Postgres
const categoryQuery = div => `
SELECT EXISTS (
SELECT 1 FROM "SystemUserRoles" sur
WHERE "SystemUser" = $1 AND
EXISTS (
SELECT 1 FROM "Permission" perm
WHERE sur."Roles" = "Role" AND
(SELECT NOT "Blocked" FROM "Role" WHERE "Id" = perm."Role") AND
"Category" = (
SELECT "Id" FROM "Category" WHERE "Name" = $2
) AND "Access" & $3 <> 0 ${div ? `AND ${div} = $4` : ''}
)
)`;
const actionQuery = `
SELECT EXISTS (
SELECT 1 FROM "SystemUserRoles" sur
WHERE "SystemUser" = $1 AND
EXISTS (
SELECT 1 FROM "Permission" perm
WHERE sur."Roles" = "Role" AND
(SELECT NOT "Blocked" FROM "Role" WHERE "Id" = perm."Role") AND
"Category" = (
SELECT "Id" FROM "Category" WHERE "Name" = $2
) AND EXISTS (
SELECT 1 FROM "PermissionActions"
WHERE "Permission" = perm."Id" AND
"Actions" IN (
SELECT "Id" FROM "Action" WHERE "Name" = $3
)
)
)
)`;
const categoryFilterQuery = `
SELECT DISTINCT "Category"."Name" FROM "Category"
INNER JOIN "Permission" ON "Permission"."Category" = "Category"."Id"
INNER JOIN (SELECT unnest($2::text[]) "Name") "PossibleCategory" ON
"PossibleCategory"."Name" = "Category"."Name"
WHERE "Permission"."Role" IN (
SELECT "Roles" FROM "SystemUserRoles" WHERE "SystemUser" = $1
)`;
const categoryPermissionFilterQuery = `
SELECT "Category"."Name", bit_or("Permission"."Access") "Flags" FROM "Category"
INNER JOIN "Permission" ON "Permission"."Category" = "Category"."Id"
INNER JOIN (SELECT unnest($2::text[]) "Name") "PossibleCategory" ON
"PossibleCategory"."Name" = "Category"."Name"
WHERE "Permission"."Role" IN (
SELECT "Roles" FROM "SystemUserRoles" WHERE "SystemUser" = $1
)
GROUP BY "Category"."Name"
`;
const actionFilterQuery = `
SELECT DISTINCT ON ("Category"."Name", "Action"."Name")
"Category"."Name" "Category", "Action"."Name" "Action" FROM "Category"
INNER JOIN "Permission" ON "Permission"."Category" = "Category"."Id"
INNER JOIN "PermissionActions" ON
"PermissionActions"."Permission" = "Permission"."Id"
INNER JOIN "Action" ON "Action"."Id" = "PermissionActions"."Actions"
INNER JOIN unnest($2::text[], $3::text[])
"SchemaActions" ("Category", "Action") ON
"SchemaActions"."Category" = "Category"."Name" AND
"SchemaActions"."Action" = "Action"."Name"
WHERE "Permission"."Role" IN (
SELECT "Roles" FROM "SystemUserRoles" WHERE "SystemUser" = $1
)`;
const applicationFilterQuery = `
SELECT DISTINCT "Application"."Name" FROM "Application"
INNER JOIN "RoleApplications" ra ON "Application"."Id" = ra."Applications"
INNER JOIN (SELECT unnest($2::text[]) "Name") "PossibleApp" ON
"PossibleApp"."Name" = "Application"."Name"
WHERE ra."Role" IN (
SELECT "Roles" FROM "SystemUserRoles" WHERE "SystemUser" = $1
)`;
const checkPermission = (provider, accessType, category, userId, callback) => {
const flag = permissionFlags[accessType];
provider.pool.query(categoryQuery(), [userId, category, flag], (err, res) => {
if (err) {
provider.systemLogger(err);
callback(err);
} else {
callback(null, res.rows[0].exists);
}
});
};
const validateIdString = id => new Uint64(id).toString() === id;
const validateQuery = (provider, category, query) => {
const categorySchema = provider.schema.categories.get(category);
const catalogField = categorySchema.catalog;
if (
catalogField &&
(!query[catalogField] || !validateIdString(query[catalogField]))
) {
return false;
}
const subsystemField = categorySchema.subsystem;
if (
subsystemField &&
(!query[subsystemField] || !validateIdString(query[subsystemField]))
) {
return false;
}
return true;
};
const checkPermissionComplex = (
provider,
accessType,
category,
userId,
{ record, id, isPatch, isQuery },
callback
) => {
const getRecord = (category, id, callback) => {
provider.pool.query(
`SELECT * FROM ${escapeIdentifier(category)} WHERE "Id" = $1`,
[id],
(err, res) => {
if (err) {
callback(err);
} else {
callback(null, res.rows[0]);
}
}
);
};
runIf(id, getRecord, category, id, (err, dbRec) => {
if (err) {
provider.systemLogger(err);
callback(err);
return;
}
if (dbRec) record = dbRec;
if (!record || (isQuery && !validateQuery(provider, category, record))) {
callback(null, false);
return;
}
const categorySchema = provider.schema.categories.get(category);
const flag = permissionFlags[accessType];
const ops = [];
if (categorySchema.catalog) {
const catalogValue = record[categorySchema.catalog];
if (!isPatch || catalogValue) {
ops.push({
query: categoryQuery(escapeIdentifier('Catalog')),
args: [userId, category, flag, catalogValue],
});
}
}
if (categorySchema.subsystem) {
const subsystemValue = record[categorySchema.subsystem];
if (!isPatch || subsystemValue) {
ops.push({
query: categoryQuery(escapeIdentifier('Subsystem')),
args: [userId, category, flag, subsystemValue],
});
}
}
metasync.map(
ops,
(op, callback) => {
provider.pool.query(op.query, op.args, (err, res) => {
if (err) {
callback(err);
} else {
callback(null, res.rows[0].exists);
}
});
},
(err, res) => {
if (err) {
provider.systemLogger(err);
callback(err);
} else {
callback(null, !res.includes(false));
}
}
);
});
};
const checkExecutePermission = (
provider,
category,
action,
userId,
callback
) => {
if (category === null) {
process.nextTick(callback, null, provider.schema.actions.has(action));
return;
}
provider.pool.query(actionQuery, [userId, category, action], (err, res) => {
if (err) {
provider.systemLogger(err);
callback(err);
return;
}
callback(null, res.rows[0].exists);
});
};
const filterCategories = (provider, categories, userId, callback) => {
provider.pool.query(categoryFilterQuery, [userId, categories], (err, res) => {
if (err) {
provider.systemLogger(err);
callback(err);
return;
}
callback(null, res.rows.map(r => r.Name));
});
};
const filterCategoriesWithPermissions = (
provider,
categories,
userId,
callback
) => {
provider.pool.query(
categoryPermissionFilterQuery,
[userId, categories],
(err, res) => {
if (err) {
provider.systemLogger(err);
callback(err);
return;
}
const result = {};
for (const { Name, Flags } of res.rows) {
result[Name] = String(Flags);
}
callback(null, result);
}
);
};
const filterApplications = (provider, applications, userId, callback) => {
provider.pool.query(
applicationFilterQuery,
[userId, applications],
(err, res) => {
if (err) {
provider.systemLogger(err);
callback(err);
return;
}
callback(null, res.rows.map(r => r.Name));
}
);
};
const filterActions = (provider, actions, userId, callback) => {
const preparedActions = [[], []];
for (const [cat, acts] of Object.entries(actions.private)) {
// TODO: replace with common.pushSame() after it becomes available there
// See https://github.com/metarhia/common/pull/257
const from = preparedActions[0].length;
preparedActions[0].length += acts.length;
preparedActions[0].fill(cat, from);
preparedActions[1].push(...acts);
}
provider.pool.query(
actionFilterQuery,
[userId, ...preparedActions],
(err, res) => {
if (err) {
provider.systemLogger(err);
callback(err);
return;
}
const result = {
public: actions.public,
private: {},
};
for (const row of res.rows) {
if (!result.private[row.Category]) {
result.private[row.Category] = [row.Action];
} else {
result.private[row.Category].push(row.Action);
}
}
callback(null, result);
}
);
};
module.exports = {
checkPermission,
checkPermissionComplex,
checkExecutePermission,
filterCategories,
filterCategoriesWithPermissions,
filterActions,
filterApplications,
};