diff --git a/packages/backend/src/api/APIError.js b/packages/backend/src/api/APIError.js index 4e36ac2dc..29f4a3563 100644 --- a/packages/backend/src/api/APIError.js +++ b/packages/backend/src/api/APIError.js @@ -28,6 +28,13 @@ const { quot } = require("../util/strutil"); */ module.exports = class APIError { static codes = { + // General + 'unknown_error': { + status: 500, + message: () => `An unknown error occurred`, + }, + + // Unorganized 'item_with_same_name_exists': { status: 409, message: ({ entry_name }) => entry_name diff --git a/packages/backend/src/routers/hosting/puter-site.js b/packages/backend/src/routers/hosting/puter-site.js index f4e57fbd4..2129c907c 100644 --- a/packages/backend/src/routers/hosting/puter-site.js +++ b/packages/backend/src/routers/hosting/puter-site.js @@ -24,7 +24,7 @@ const { Context } = require("../../util/context"); const { NodeInternalIDSelector, NodePathSelector } = require("../../filesystem/node/selectors"); const { TYPE_DIRECTORY } = require("../../filesystem/FSNodeContext"); const { LLRead } = require("../../filesystem/ll_operations/ll_read"); -const { Actor, UserActorType } = require("../../services/auth/Actor"); +const { Actor, UserActorType, SiteActorType } = require("../../services/auth/Actor"); const APIError = require("../../api/APIError"); class PuterSiteMiddleware extends AdvancedBase { @@ -145,14 +145,60 @@ class PuterSiteMiddleware extends AdvancedBase { await target_node.get('name') ); res.set('Content-Type', contentType); + + const acl_config = { + no_acl: true, + actor: null, + }; + + if ( site.protected ) { + const svc_auth = req.services.get('auth'); + const token = req.query['puter.auth.token']; + + acl_config.no_acl = false; + + if ( ! token ) { + const e = APIError.create('token_missing'); + return this.respond_error_({ req, res, e }); + } + + const app_actor = + await svc_auth.authenticate_from_token(token); + + const user_actor = + app_actor.get_related_actor(UserActorType); + + const svc_permission = req.services.get('permission'); + const perm = await (async () => { + if ( user_actor.type.user.id === site.user_id ) { + return {}; + } + + return await svc_permission.check( + user_actor, `site:uid#${site.uuid}:access` + ); + })(); + + if ( ! perm ) { + const e = APIError.create('forbidden'); + return this.respond_error_({ req, res, e }); + } + + const site_actor = await Actor.create(SiteActorType, { site }); + acl_config.actor = site_actor; + + console.log('THE SITE ACTOR?', site_actor); + + Object.freeze(acl_config); + } const ll_read = new LLRead(); + // const actor = Actor.adapt(req.user); + console.log('what user?', req.user); + console.log('what actor?', acl_config.actor); const stream = await ll_read.run({ - no_acl: true, - actor: new Actor({ - user_uid: req.user ? req.user.uuid : null, - type: new UserActorType({ user: req.user }), - }), + no_acl: acl_config.no_acl, + actor: acl_config.actor, fsNode: target_node, }); @@ -189,6 +235,19 @@ class PuterSiteMiddleware extends AdvancedBase { return res.end(); } + + respond_error_ ({ req, res, e }) { + if ( ! (e instanceof APIError) ) { + // TODO: alarm here + e = APIError.create('unknown_error'); + } + + res.redirect(`${config.origin}?${e.querystringize({ + ...(req.query['puter.app_instance_id'] ? { + ['error_from_within_iframe']: true, + } : {}) + })}`); + } } module.exports = app => { diff --git a/packages/backend/src/services/PuterSiteService.js b/packages/backend/src/services/PuterSiteService.js index 1d8643ccb..dd0dfb6be 100644 --- a/packages/backend/src/services/PuterSiteService.js +++ b/packages/backend/src/services/PuterSiteService.js @@ -16,7 +16,10 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +const { NodeInternalIDSelector, NodeUIDSelector } = require("../filesystem/node/selectors"); const { Context } = require("../util/context"); +const { SiteActorType } = require("./auth/Actor"); +const { PermissionUtil, PermissionRewriter, PermissionImplicator } = require("./auth/PermissionService"); const BaseService = require("./BaseService"); const { DB_WRITE } = require("./database/consts"); @@ -24,6 +27,65 @@ class PuterSiteService extends BaseService { async _init () { const services = this.services; this.db = services.get('database').get(DB_WRITE, 'sites'); + + const svc_fs = services.get('filesystem'); + + // Rewrite site permissions specified by name + const svc_permission = this.services.get('permission'); + svc_permission.register_rewriter(PermissionRewriter.create({ + matcher: permission => { + if ( ! permission.startsWith('site:') ) return false; + const [_, specifier] = PermissionUtil.split(permission); + if ( specifier.startsWith('uid#') ) return false; + return true; + }, + rewriter: async permission => { + const [_1, name, ...rest] = PermissionUtil.split(permission); + const sd = await this.get_subdomain(name); + return PermissionUtil.join( + _1, `uid#${sd.uuid}`, ...rest, + ); + }, + })); + + // Imply that sites can read their own files + svc_permission.register_implicator(PermissionImplicator.create({ + matcher: permission => { + return permission.startsWith('fs:'); + }, + checker: async ({ actor, permission }) => { + if ( !(actor.type instanceof SiteActorType) ) { + return undefined; + } + + const [_, uid, lvl] = PermissionUtil.split(permission); + const node = await svc_fs.node(new NodeUIDSelector(uid)); + + if ( !['read','list','see'].includes(lvl) ) { + return undefined; + } + + if ( ! await node.exists() ) { + return undefined; + } + + const site_node = await svc_fs.node( + new NodeInternalIDSelector( + 'mysql', + actor.type.site.root_dir_id, + ) + ); + + if ( await site_node.is(node) ) { + return {}; + } + if ( await site_node.is_above(node) ) { + return {}; + } + + return undefined; + }, + })); } async get_subdomain (subdomain) { @@ -40,6 +102,15 @@ class PuterSiteService extends BaseService { if ( rows.length === 0 ) return null; return rows[0]; } + + // async get_subdomain_by_uid (uid) { + // const rows = await this.db.read( + // `SELECT * FROM subdomains WHERE uuid = ? LIMIT 1`, + // [uid] + // ); + // if ( rows.length === 0 ) return null; + // return rows[0]; + // } } module.exports = { diff --git a/packages/backend/src/services/auth/Actor.js b/packages/backend/src/services/auth/Actor.js index 7f83b8a33..fa748cda3 100644 --- a/packages/backend/src/services/auth/Actor.js +++ b/packages/backend/src/services/auth/Actor.js @@ -182,6 +182,17 @@ class AccessTokenActorType { } } +class SiteActorType { + constructor (o, ...a) { + for ( const k in o ) { + this[k] = o[k]; + } + } + get uid () { + return `site:` + this.site.name + } +} + Actor.adapt = function (actor) { actor = actor || Context.get('actor'); @@ -208,4 +219,5 @@ module.exports = { UserActorType, AppUnderUserActorType, AccessTokenActorType, + SiteActorType, } diff --git a/packages/backend/src/services/auth/PermissionService.js b/packages/backend/src/services/auth/PermissionService.js index d7f55e14b..0f45f9b33 100644 --- a/packages/backend/src/services/auth/PermissionService.js +++ b/packages/backend/src/services/auth/PermissionService.js @@ -20,7 +20,7 @@ const { get_user, get_app } = require("../../helpers"); const { Context } = require("../../util/context"); const BaseService = require("../BaseService"); const { DB_WRITE } = require("../database/consts"); -const { UserActorType, Actor, AppUnderUserActorType, AccessTokenActorType } = require("./Actor"); +const { UserActorType, Actor, AppUnderUserActorType, AccessTokenActorType, SiteActorType } = require("./Actor"); const default_implicit_user_app_permissions = { 'driver:helloworld:greet': {}, @@ -214,15 +214,17 @@ class PermissionUtil { } class PermissionService extends BaseService { - async _init () { - this.db = this.services.get('database').get(DB_WRITE, 'permissions'); - this._register_commands(this.services.get('commands')); - + _construct () { this._permission_rewriters = []; this._permission_implicators = []; this._permission_exploders = []; } + async _init () { + this.db = this.services.get('database').get(DB_WRITE, 'permissions'); + this._register_commands(this.services.get('commands')); + } + async _rewrite_permission (permission) { for ( const rewriter of this._permission_rewriters ) { if ( ! rewriter.matches(permission) ) continue; @@ -292,6 +294,12 @@ class PermissionService extends BaseService { return await this.check_user_app_permission(actor, app_uid, permission); } + + if ( actor.type instanceof SiteActorType ) { + return await this.check_site_permission(actor, permission); + } + + console.log ('WHAT ACTOR TYPE THEN???', actor.type); throw new Error('unrecognized actor type'); } @@ -421,6 +429,29 @@ class PermissionService extends BaseService { return rows[0].extra; } + + async check_site_permission (actor, permission) { + permission = await this._rewrite_permission(permission); + // const parent_perms = this.get_parent_permissions(permission); + const parent_perms = await this.get_higher_permissions(permission); + + // Check implicit permissions + for ( const parent_perm of parent_perms ) { + if ( implicit_user_permissions[parent_perm] ) { + return implicit_user_permissions[parent_perm]; + } + } + + for ( const implicator of this._permission_implicators ) { + if ( ! implicator.matches(permission) ) continue; + const implied = await implicator.check({ + actor, + permission, + recurse: this.check.bind(this), + }); + if ( implied ) return implied; + } + } async grant_user_app_permission (actor, app_uid, permission, extra = {}, meta) { permission = await this._rewrite_permission(permission);