dev: add /acl/stat-user-user

This new endpoint shows permissions granted by the current user to the
specified user in the scope of a specified filesystem node, including
permissions on the affecting parent nodes.

This is related to two future tasks:
- prevent users from sharing a file with the same user twice
- add an endpoint to modify the access mode of a shared file
This commit is contained in:
KernelDeimos 2024-11-20 13:53:01 -05:00
parent ea4cde82fc
commit 4396630453
2 changed files with 115 additions and 0 deletions

View File

@ -17,13 +17,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const APIError = require("../../api/APIError");
const FSNodeParam = require("../../api/filesystem/FSNodeParam");
const { NodePathSelector } = require("../../filesystem/node/selectors");
const { get_user } = require("../../helpers");
const configurable_auth = require("../../middleware/configurable_auth");
const { Context } = require("../../util/context");
const { Endpoint } = require("../../util/expressutil");
const BaseService = require("../BaseService");
const { AppUnderUserActorType, UserActorType, Actor, SystemActorType, AccessTokenActorType } = require("./Actor");
const { PermissionUtil } = require("./PermissionService");
class ACLService extends BaseService {
static MODULES = {
express: require('express'),
};
async _init () {
const svc_featureFlag = this.services.get('feature-flag');
svc_featureFlag.register('public-folders', {
@ -44,6 +52,85 @@ class ACLService extends BaseService {
});
}
async ['__on_install.routes'] (_, { app }) {
const r_acl = (() => {
const require = this.require;
const express = require('express');
return express.Router();
})();
app.use('/acl', r_acl);
Endpoint({
route: '/stat-user-user',
methods: ['POST'],
mw: [configurable_auth()],
handler: async (req, res) => {
// Only user actor is allowed
if ( ! (req.actor.type instanceof UserActorType) ) {
return res.status(403).json({
error: 'forbidden',
});
}
const holder_user = await get_user({
username: req.body.user,
});
if ( ! holder_user ) {
throw APIError.create('user_does_not_exist', null, {
username: req.body.user,
});
}
const issuer = req.actor;
const holder = new Actor({
type: new UserActorType({
user: holder_user,
}),
});
const node = await (new FSNodeParam('path')).consolidate({
req,
getParam: () => req.body.resource,
});
const permissions = await this.stat_user_user(issuer, holder, node);
res.json({ permissions });
}
}).attach(r_acl);
}
async stat_user_user (issuer, holder, resource) {
const svc_perm = this.services.get('permission');
if ( ! (issuer.type instanceof UserActorType) ) {
throw new Error('issuer must be a UserActorType');
}
if ( ! (holder.type instanceof UserActorType) ) {
throw new Error('holder must be a UserActorType');
}
const permissions = {};
let perm_fsNode = resource;
while ( ! await perm_fsNode.get('is-root') ) {
const prefix = PermissionUtil.join('fs', await perm_fsNode.get('uid'));
const these_permissions = await
svc_perm.query_issuer_holder_permissions_by_prefix(issuer, holder, prefix);
if ( these_permissions.length > 0 ) {
permissions[await perm_fsNode.get('path')] = these_permissions;
}
perm_fsNode = await perm_fsNode.getParent();
}
return permissions;
}
async _check_fsNode (actor, fsNode, mode) {
const context = Context.get();

View File

@ -638,6 +638,34 @@ class PermissionService extends BaseService {
return retval;
}
/**
* List the permissions that the specified actor (the "issuer")
* has granted to the specified user (the "holder") which have
* some specified prefix in the permission key (ex: "fs:FILE-UUID")
*
* Note that if the prefix contains a literal '%' character
* the behavior may not be as expected.
*
* This is a "flat" (non-cascading) view.
*
* @param {*} issuer
* @param {*} holder
* @param {*} prefix
* @returns
*/
async query_issuer_holder_permissions_by_prefix (issuer, holder, prefix) {
const user_perms = await this.db.read(
'SELECT permission ' +
'FROM `user_to_user_permissions` ' +
'WHERE issuer_user_id = ? ' +
'AND holder_user_id = ? ' +
'AND permission LIKE ?',
[issuer.type.user.id, holder.type.user.id, prefix + '%'],
);
return user_perms.map(row => row.permission);
}
async get_higher_permissions (permission) {
const higher_perms = new Set()