Add functions to check operation access via organisations

This commit is contained in:
D. Berge
2025-07-11 20:32:14 +02:00
parent 9ad17de4cb
commit 0b7e9e1d01
2 changed files with 107 additions and 0 deletions

View File

@@ -6,4 +6,5 @@ module.exports = {
delete: require('./delete'),
summary: require('./summary'),
configuration: require('./configuration'),
organisations: require('./organisations'),
}

View File

@@ -0,0 +1,106 @@
const { setSurvey, pool } = require('../connection');
const { ALERT, ERROR, WARNING, NOTICE, INFO, DEBUG } = require('DOUGAL_ROOT/debug')(__filename);
// Cache the per-project organisations access here
let projectsCache;
// Prime the cache directly from the database
async function read () {
DEBUG("Reading projects from database");
const text = `
SELECT
pid,
meta->'organisations' AS organisations
FROM public.projects;
`;
const res = await pool.query(text);
return Object.fromEntries(res.rows.map( row => [ row.pid, row.organisations ] ));
}
/*
* Called from the database events project configuration
* change handler
*/
function setCache (projects) {
DEBUG("Setting project organisations cache via setCache()");
projectsCache = Object.fromEntries(Object.entries(projects).map( row => [ row[0], row[1].organisations ] ));
}
/*
* Use this function to read `projectsCache`. Do NOT
* read `projectsCache` directly as it might be undefined
*/
async function projectOrganisations (pid) {
if (!projectsCache) {
DEBUG("Priming projectsCache");
projectsCache = await read();
}
return projectsCache[pid] ?? {}; // Every project should have an `organisations` property, but…
}
/** Check whether the user has access to the required operation
* @a userOrgs is the user's organisations
* @a projectOrgs is the project's organisations
* @a operation is the desired operation (read, write, etc.)
*
* @return `true` is user has access to `operation` through
* a common organisation, `false` otherwise.
*/
function access (userOrgs, projectOrgs, operation) {
console.log("userOrgs", userOrgs);
console.log("projectOrgs", projectOrgs);
console.log("operation", operation);
for (const userOrg in userOrgs) {
if (userOrg in projectOrgs) {
// Found an organisation in common between user and project
// (there might be many)
if (projectOrgs[userOrg][operation] == true && userOrgs[userOrg][operation] == true) {
// For this one, the project grants the required operation
// access to the organisation, and the organisation grants the
// required operation access to the user, so authorisation is
// given.
console.log("Access granted via organisation", userOrg, projectOrgs[userOrg][operation], userOrgs[userOrg][operation]);
return true;
}
}
}
if ("*" in userOrgs) {
// Aha! A wildcard user
// Return true if at least one organisation grants access
// to this operation
console.log("Checking via wildcard");
return (Object.values(projectOrgs).some( org => org[operation] ))
}
return false;
}
/** Check whether a user has access to the project given by `pid`
*/
async function orgAccess (userOrgs, pid, operation) {
const projectOrgs = await projectOrganisations(pid);
return access(userOrgs, projectOrgs, operation);
}
/*
* Filter an array of objects by organisation access to a given operation
*/
function orgFilter (userOrgs, list, operation, fn = (item) => item.organisations ) {
console.log("orgFilter");
console.log("userOrgs", userOrgs);
console.log("list", list);
console.log("operation", operation);
console.log("fn", fn);
return list.filter ( (item) => access(userOrgs, fn(item), operation) );
}
module.exports = {
setCache,
projectOrganisations,
orgAccess,
orgFilter
};