From c9ded89b22bb822c20aea379a17a8bdf74a658de Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sat, 20 Jul 2024 00:55:26 -0400 Subject: [PATCH] feat: grant user driver perms from admin --- src/backend/src/config.js | 3 + src/backend/src/routers/signup.js | 19 ++++ .../src/services/DefaultUserService.js | 80 ++++++++++++++++ .../database/SqliteDatabaseAccessService.js | 11 ++- .../sqlite_setup/0026_user-groups.dbmig.js | 95 +++++++++++++++++++ src/backend/src/util/structutil.js | 35 +++++++ 6 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 src/backend/src/services/database/sqlite_setup/0026_user-groups.dbmig.js create mode 100644 src/backend/src/util/structutil.js diff --git a/src/backend/src/config.js b/src/backend/src/config.js index 2ff733f3..c23f357c 100644 --- a/src/backend/src/config.js +++ b/src/backend/src/config.js @@ -28,6 +28,9 @@ config.servers = []; // Will disable the auto-generated temp users. If a user lands on the site, they will be required to sign up or log in. config.disable_temp_users = false; +config.default_user_group = '78b1b1dd-c959-44d2-b02c-8735671f9997'; +config.default_temp_group = 'b7220104-7905-4985-b996-649fdcdb3c8f'; + config.max_file_size = 100_000_000_000, config.max_thumb_size = 1_000, config.max_fsentry_name_length = 767, diff --git a/src/backend/src/routers/signup.js b/src/backend/src/routers/signup.js index e94b7b95..50a503b4 100644 --- a/src/backend/src/routers/signup.js +++ b/src/backend/src/routers/signup.js @@ -214,6 +214,14 @@ module.exports = eggspress(['/signup'], { 'UPDATE `user` SET `last_activity_ts` = now() WHERE id=? LIMIT 1', [insert_res.insertId] ); + + // TODO: cache group id + const svc_group = req.services.get('group'); + await svc_group.add_users({ + uid: req.body.is_temp ? + config.default_temp_group : config.default_user_group, + users: [req.body.username] + }); } // ----------------------------------- // Pseudo User converting @@ -244,6 +252,17 @@ module.exports = eggspress(['/signup'], { ] ); + // TODO: cache group ids + const svc_group = req.services.get('group'); + await svc_group.remove_users({ + uid: config.default_temp_group, + users: [req.body.username], + }); + await svc_group.add_users({ + uid: config.default_user_group, + users: [req.body.username] + }); + // record activity db.write('UPDATE `user` SET `last_activity_ts` = now() WHERE id=? LIMIT 1', [pseudo_user.id]); invalidate_cached_user_by_id(pseudo_user.id); diff --git a/src/backend/src/services/DefaultUserService.js b/src/backend/src/services/DefaultUserService.js index c27f7fef..9f76a33d 100644 --- a/src/backend/src/services/DefaultUserService.js +++ b/src/backend/src/services/DefaultUserService.js @@ -16,16 +16,55 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +const { QuickMkdir } = require("../filesystem/hl_operations/hl_mkdir"); +const { HLWrite } = require("../filesystem/hl_operations/hl_write"); +const { NodePathSelector } = require("../filesystem/node/selectors"); const { surrounding_box } = require("../fun/dev-console-ui-utils"); const { get_user, generate_system_fsentries, invalidate_cached_user } = require("../helpers"); const { Context } = require("../util/context"); const { asyncSafeSetInterval } = require("../util/promise"); +const { buffer_to_stream } = require("../util/streamutil"); const BaseService = require("./BaseService"); const { Actor, UserActorType } = require("./auth/Actor"); const { DB_WRITE } = require("./database/consts"); const USERNAME = 'admin'; +const DEFAULT_FILES = { + '.policy': { + 'drivers.json': JSON.stringify({ + "temp": { + "kv": { + "rate-limit": { + "max": 1000, + "period": 30000 + } + }, + "es": { + "date-limit": { + "max": 1000, + "period": 30000 + } + }, + }, + "user": { + "kv": { + "rate-limit": { + "max": 3000, + "period": 30000 + } + }, + "es": { + "rate-limit": { + "max": 3000, + "period": 30000 + } + } + } + }, undefined, ' '), + } +}; + class DefaultUserService extends BaseService { static MODULES = { bcrypt: require('bcrypt'), @@ -100,6 +139,7 @@ class DefaultUserService extends BaseService { users: [USERNAME] }); const user = await get_user({ username: USERNAME, cached: false }); + const actor = Actor.adapt(user); const tmp_password = await this.get_tmp_password_(user); const bcrypt = require('bcrypt'); const password_hashed = await bcrypt.hash(tmp_password, 8); @@ -112,6 +152,46 @@ class DefaultUserService extends BaseService { ); user.password = password_hashed; await generate_system_fsentries(user); + // generate default files for admin user + const svc_fs = this.services.get('filesystem'); + const make_tree_ = async ({ components, tree }) => { + const parent = await svc_fs.node( + new NodePathSelector('/'+components.join('/')), + ); + for ( const k in tree ) { + if ( typeof tree[k] === 'string' ) { + const buffer = Buffer.from(tree[k], 'utf-8'); + const hl_write = new HLWrite(); + await hl_write.run({ + destination_or_parent: parent, + specified_name: k, + file: { + size: buffer.length, + stream: buffer_to_stream(buffer), + }, + user, + }); + } else { + const hl_qmkdir = new QuickMkdir(); + await hl_qmkdir.run({ + parent, + path: k, + }); + const components_ = [...components, k]; + await make_tree_({ + components: components_, + tree: tree[k], + }); + } + + } + }; + await Context.get().sub({ user, actor }).arun(async () => { + await make_tree_({ + components: ['admin'], + tree: DEFAULT_FILES + }); + }); invalidate_cached_user(user); await new Promise(rslv => setTimeout(rslv, 2000)); return user; diff --git a/src/backend/src/services/database/SqliteDatabaseAccessService.js b/src/backend/src/services/database/SqliteDatabaseAccessService.js index 7de3982b..c9d2f8e1 100644 --- a/src/backend/src/services/database/SqliteDatabaseAccessService.js +++ b/src/backend/src/services/database/SqliteDatabaseAccessService.js @@ -18,6 +18,7 @@ */ const { es_import_promise } = require("../../fun/dev-console-ui-utils"); const { surrounding_box } = require("../../fun/dev-console-ui-utils"); +const structutil = require("../../util/structutil"); const { BaseDatabaseAccessService } = require("./BaseDatabaseAccessService"); class SqliteDatabaseAccessService extends BaseDatabaseAccessService { @@ -42,7 +43,7 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService { this.db = new Database(this.config.path); // Database upgrade logic - const TARGET_VERSION = 23; + const TARGET_VERSION = 24; if ( do_setup ) { this.log.noticeme(`SETUP: creating database at ${this.config.path}`); @@ -71,7 +72,8 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService { '0022_dev-center-max.sql', '0023_fix-kv.sql', '0024_default-groups.sql', - '0025_system-user.dbmig.js' + '0025_system-user.dbmig.js', + '0026_user-groups.dbmig.js', ].map(p => path_.join(__dirname, 'sqlite_setup', p)); const fs = require('fs'); for ( const filename of sql_files ) { @@ -180,6 +182,10 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService { upgrade_files.push('0025_system-user.dbmig.js'); } + if ( user_version <= 23 ) { + upgrade_files.push('0026_user-groups.dbmig.js'); + } + if ( upgrade_files.length > 0 ) { this.log.noticeme(`Database out of date: ${this.config.path}`); this.log.noticeme(`UPGRADING DATABASE: ${user_version} -> ${TARGET_VERSION}`); @@ -299,6 +305,7 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService { read: this.read.bind(this), write: this.write.bind(this), log: this.log, + structutil, }); await vm.runInContext(contents, context); } diff --git a/src/backend/src/services/database/sqlite_setup/0026_user-groups.dbmig.js b/src/backend/src/services/database/sqlite_setup/0026_user-groups.dbmig.js new file mode 100644 index 00000000..204c55f7 --- /dev/null +++ b/src/backend/src/services/database/sqlite_setup/0026_user-groups.dbmig.js @@ -0,0 +1,95 @@ +const { insertId: temp_group_id } = await write( + 'INSERT INTO `group` (`uid`, `owner_user_id`, `extra`, `metadata`) '+ + 'VALUES (?, ?, ?, ?)', + [ + 'b7220104-7905-4985-b996-649fdcdb3c8f', + 1, + '{"critical": true, "type": "default", "name": "temp"}', + '{"title": "Guest", "color": "#777777"}' + ] +); +const [{id: system_user_id}] = await read( + "SELECT id FROM `user` WHERE username='system'" +); +const [{id: user_group_id}] = await read( + 'SELECT id FROM `group` WHERE uid=?', + ['78b1b1dd-c959-44d2-b02c-8735671f9997'] +); + +const user_types = structutil.apply_keys( + ['name', 'group_id'], + ['temp', temp_group_id], + ['user', user_group_id], +); +const drivers = structutil.apply_keys( + ['driver_id', 'selector'], + ['driver:puter-kvstore', 'kv'], + ['driver:puter-notifications', 'es'], + ['driver:puter-apps', 'es'], + ['driver:puter-subdomains', 'es'], +); + +const perms = structutil.cart_product( + [user_types, drivers]); + +for ( const perm of perms ) { + const [user_type, driver] = perm; + log.info('permission info', { user_type, driver }); + debugger; + // temp user drivers + await write( + 'INSERT INTO `user_to_group_permissions` ' + + '(`user_id`, `group_id`, `permission`, `extra`) ' + + 'VALUES (?, ?, ?, ?)', + [ + system_user_id, user_type.group_id, + driver.driver_id, + JSON.stringify({ + policy: { + $: 'json-address', + path: '/admin/.policy/drivers.json', + selector: user_type.name + '.' + + driver.selector, + } + }), + ] + ); +} + +/* +// temp user drivers +await write( + 'INSERT INTO `user_to_group_permissions` ' + + '(`user_id`, `group_id`, `permission`, `extra`) ' + + 'VALUES (?, ?, ?, ?)', + [ + system_user_id, temp_group_id, + 'driver:puter-kvstore', + JSON.stringify({ + policy: { + $: 'json-address', + path: '/admin/.policy/drivers.json', + selector: 'temp.kv', + } + }), + ] +); + +// registered user drivers +await write( + 'INSERT INTO `user_to_group_permissions` ' + + '(`user_id`, `group_id`, `permission`, `extra`) ' + + 'VALUES (?, ?, ?, ?)', + [ + system_user_id, user_group_id, + 'driver:puter-kvstore', + JSON.stringify({ + policy: { + $: 'json-address', + path: '/admin/.policy/drivers.json', + selector: 'user.kv', + } + }), + ] +); +*/ diff --git a/src/backend/src/util/structutil.js b/src/backend/src/util/structutil.js new file mode 100644 index 00000000..21425018 --- /dev/null +++ b/src/backend/src/util/structutil.js @@ -0,0 +1,35 @@ +const cart_product = (obj) => { + // Get array of keys + let keys = Object.keys(obj); + + // Generate the Cartesian Product + return keys.reduce((acc, key) => { + let appendArrays = Array.isArray(obj[key]) ? obj[key] : [obj[key]]; + + let newAcc = []; + acc.forEach(arr => { + appendArrays.forEach(item => { + newAcc.push([...arr, item]); + }); + }); + + return newAcc; + }, [[]]); // start with the "empty product" +} + +const apply_keys = (keys, ...entries) => { + const l = []; + for ( const entry of entries ) { + const o = {}; + for ( let i=0 ; i < keys.length ; i++ ) { + o[keys[i]] = entry[i]; + } + l.push(o); + } + return l; +} + +module.exports = { + cart_product, + apply_keys, +};