diff --git a/src/backend/src/om/entitystorage/AppLimitedES.js b/src/backend/src/om/entitystorage/AppLimitedES.js index c98effb7a..771e52295 100644 --- a/src/backend/src/om/entitystorage/AppLimitedES.js +++ b/src/backend/src/om/entitystorage/AppLimitedES.js @@ -16,6 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +const APIError = require('../../api/APIError'); const { AppUnderUserActorType } = require('../../services/auth/Actor'); const { PermissionUtil } = require('../../services/auth/permissionUtils.mjs'); const { Context } = require('../../util/context'); @@ -25,6 +26,8 @@ const { Entity } = require('./Entity'); class AppLimitedES extends BaseES { + // #region read operations + // Limit selection to entities owned by the app of the current actor. async select (options) { const actor = Context.get('actor'); @@ -90,6 +93,57 @@ class AppLimitedES extends BaseES { return entity; } + + // #endregion + + // #region write operations + + // Limit edit to entities owned by the app of the current actor + async upsert (entity, extra) { + const actor = Context.get('actor'); + if ( actor.type instanceof AppUnderUserActorType ) { + const { old_entity } = extra; + if ( old_entity ) { + await this._check_edit_allowed({ old_entity }); + } + } + return await this.upstream.upsert(entity, extra); + } + async delete (uid, extra) { + const actor = Context.get('actor'); + if ( actor.type instanceof AppUnderUserActorType ) { + const { old_entity } = extra; + await this._check_edit_allowed({ old_entity }); + } + return await this.upstream.delete(uid, extra); + } + async _check_edit_allowed ({ old_entity }) { + const actor = Context.get('actor'); + + // Maybe the app has been granted write access to all the user's apps + // (in which case we return early) + { + const svc_permission = Context.get('services').get('permission'); + const perm = PermissionUtil.join('apps-of-user', actor.type.user.uuid, 'write'); + const can_write_any = await svc_permission.check(actor, perm); + if ( can_write_any ) return; + } + + // Otherwise, verify the app owner + // (or we throw an APIError) + { + const app = actor.type.app; + const app_owner = await old_entity.get('app_owner'); + let app_owner_id = app_owner?.id; + if ( app_owner instanceof Entity ) { + app_owner_id = app_owner.private_meta.mysql_id; + } + if ( ( !app_owner ) || app_owner_id !== app.id ) { + throw APIError.create('forbidden'); + } + } + } + // #endregion } module.exports = {