Просмотр исходного кода

feat: grant user driver perms from admin

KernelDeimos 10 месяцев назад
Родитель
Сommit
c9ded89b22

+ 3 - 0
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,

+ 19 - 0
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);

+ 80 - 0
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 <https://www.gnu.org/licenses/>.
  */
+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;

+ 9 - 2
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);
     }

+ 95 - 0
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',
+            }
+        }),
+    ]
+);
+*/

+ 35 - 0
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,
+};