From c374b0cbca761e7c8a47d56a09551f2e9378066a Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Thu, 27 Jun 2024 00:30:46 -0400 Subject: [PATCH] feat: add group permission endpoints --- .../src/routers/auth/grant-user-group.js | 38 +++++++++ .../src/routers/auth/revoke-user-group.js | 39 +++++++++ .../src/services/PermissionAPIService.js | 6 +- .../src/services/auth/PermissionService.js | 79 +++++++++++++++++++ 4 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 packages/backend/src/routers/auth/grant-user-group.js create mode 100644 packages/backend/src/routers/auth/revoke-user-group.js diff --git a/packages/backend/src/routers/auth/grant-user-group.js b/packages/backend/src/routers/auth/grant-user-group.js new file mode 100644 index 00000000..a44b4e53 --- /dev/null +++ b/packages/backend/src/routers/auth/grant-user-group.js @@ -0,0 +1,38 @@ +const APIError = require("../../api/APIError"); +const eggspress = require("../../api/eggspress"); +const { UserActorType } = require("../../services/auth/Actor"); +const { Context } = require("../../util/context"); + +module.exports = eggspress('/auth/grant-user-group', { + subdomain: 'api', + auth2: true, + allowedMethods: ['POST'], +}, async (req, res, next) => { + const x = Context.get(); + const svc_permission = x.get('services').get('permission'); + + // Only users can grant user-group permissions + const actor = Context.get('actor'); + if ( ! (actor.type instanceof UserActorType) ) { + throw APIError.create('forbidden'); + } + + if ( ! req.body.group_uid ) { + throw APIError.create('field_missing', null, { + key: 'group_uid' + }); + } + + if ( ! req.body.permission ) { + throw APIError.create('field_missing', null, { + key: 'permission' + }); + } + + await svc_permission.grant_user_group_permission( + actor, req.body.group_uid, req.body.permission, + req.body.extra || {}, req.body.meta || {} + ); + + res.json({}); +}); diff --git a/packages/backend/src/routers/auth/revoke-user-group.js b/packages/backend/src/routers/auth/revoke-user-group.js new file mode 100644 index 00000000..f05f2cff --- /dev/null +++ b/packages/backend/src/routers/auth/revoke-user-group.js @@ -0,0 +1,39 @@ +const APIError = require("../../api/APIError"); +const eggspress = require("../../api/eggspress"); +const { UserActorType } = require("../../services/auth/Actor"); +const { Context } = require("../../util/context"); + +module.exports = eggspress('/auth/revoke-user-group', { + subdomain: 'api', + auth2: true, + allowedMethods: ['POST'], +}, async (req, res, next) => { + const x = Context.get(); + const svc_permission = x.get('services').get('permission'); + + // Only users can grant user-user permissions + const actor = Context.get('actor'); + if ( ! (actor.type instanceof UserActorType) ) { + throw APIError.create('forbidden'); + } + + if ( ! req.body.group_uid ) { + throw APIError.create('field_missing', null, { + key: 'group_uid' + }); + } + + if ( ! req.body.permission ) { + throw APIError.create('field_missing', null, { + key: 'permission' + }); + } + + await svc_permission.revoke_user_group_permission( + actor, req.body.group_uid, req.body.permission, + req.body.meta || {} + ); + + res.json({}); +}); + diff --git a/packages/backend/src/services/PermissionAPIService.js b/packages/backend/src/services/PermissionAPIService.js index 1aa04d11..b0b76383 100644 --- a/packages/backend/src/services/PermissionAPIService.js +++ b/packages/backend/src/services/PermissionAPIService.js @@ -9,14 +9,14 @@ class PermissionAPIService extends BaseService { express: require('express'), }; - async ['__on_install.routes'] () { - const { app } = this.services.get('web-server'); - + async ['__on_install.routes'] (_, { app }) { app.use(require('../routers/auth/get-user-app-token')) app.use(require('../routers/auth/grant-user-app')) app.use(require('../routers/auth/revoke-user-app')) app.use(require('../routers/auth/grant-user-user')); app.use(require('../routers/auth/revoke-user-user')); + app.use(require('../routers/auth/grant-user-group')); + app.use(require('../routers/auth/revoke-user-group')); app.use(require('../routers/auth/list-permissions')) // track: scoping iife diff --git a/packages/backend/src/services/auth/PermissionService.js b/packages/backend/src/services/auth/PermissionService.js index 823d42cd..81470723 100644 --- a/packages/backend/src/services/auth/PermissionService.js +++ b/packages/backend/src/services/auth/PermissionService.js @@ -585,6 +585,48 @@ class PermissionService extends BaseService { ] ); } + + async grant_user_group_permission (actor, gid, permission, extra = {}, meta) { + permission = await this._rewrite_permission(permission); + const svc_group = this.services.get('group'); + const group = await svc_group.get({ uid: gid }); + if ( ! group ) { + throw new Error('group not found'); + } + + await this.db.write( + 'INSERT INTO `user_to_group_permissions` (`user_id`, `group_id`, `permission`, `extra`) ' + + 'VALUES (?, ?, ?, ?) ' + + this.db.case({ + mysql: 'ON DUPLICATE KEY UPDATE `extra` = ?', + otherwise: 'ON CONFLICT(`user_id`, `group_id`, `permission`) DO UPDATE SET `extra` = ?', + }), + [ + actor.type.user.id, + group.id, + permission, + JSON.stringify(extra), + JSON.stringify(extra), + ] + ); + + // INSERT audit table + await this.db.write( + 'INSERT INTO `audit_user_to_group_permissions` (' + + '`user_id`, `user_id_keep`, `group_id`, `group_id_keep`, ' + + '`permission`, `action`, `reason`) ' + + 'VALUES (?, ?, ?, ?, ?, ?, ?)', + [ + actor.type.user.id, + actor.type.user.id, + group.id, + group.id, + permission, + 'grant', + meta?.reason || 'granted via PermissionService', + ] + ); + } async revoke_user_user_permission (actor, username, permission, meta) { permission = await this._rewrite_permission(permission); @@ -623,6 +665,43 @@ class PermissionService extends BaseService { ); } + async revoke_user_group_permission (actor, gid, permission, meta) { + permission = await this._rewrite_permission(permission); + const svc_group = this.services.get('group'); + const group = await svc_group.get({ uid: gid }); + if ( ! group ) { + throw new Error('group not found'); + } + + // DELETE permission + await this.db.write( + 'DELETE FROM `user_to_group_permissions` ' + + 'WHERE `user_id` = ? AND `group_id` = ? AND `permission` = ?', + [ + actor.type.user.id, + group.id, + permission, + ] + ); + + // INSERT audit table + await this.db.write( + 'INSERT INTO `audit_user_to_group_permissions` (' + + '`user_id`, `user_id_keep`, `group_id`, `group_id_keep`, ' + + '`permission`, `action`, `reason`) ' + + 'VALUES (?, ?, ?, ?, ?, ?, ?)', + [ + actor.type.user.id, + actor.type.user.id, + group.id, + group.id, + permission, + 'revoke', + meta?.reason || 'revoked via PermissionService', + ] + ); + } + /** * List the users that have any permissions granted to the * specified user.