const Model = require("flitter-orm/src/model/Model"); const { ObjectId } = require("mongodb"); const uuid = require('uuid/v4'); const ActiveScope = require('../scopes/Active.scope') /* * Page Model * ------------------------------------------------------------- * Put some description here! */ class Page extends Model { static get services() { return [...super.services, "models"]; } static get schema() { // Return a flitter-orm schema here. return { UUID: {type: String, default: () => uuid()}, Name: String, OrgUserId: ObjectId, IsPublic: {type: Boolean, default: true}, IsVisibleInMenu: {type: Boolean, default: true}, ParentId: String, NodeIds: [String], CreatedAt: {type: Date, default: () => new Date}, UpdatedAt: {type: Date, default: () => new Date}, DeletedAt: Date, Active: {type: Boolean, default: true}, CreatedUserId: {type: String}, UpdateUserId: {type: String}, ChildPageIds: [String], // Menu flags noDelete: { type: Boolean, default: false }, virtual: { type: Boolean, default: false }, // Sharing properties shared_users_view: [ObjectId], shared_users_update: [ObjectId], shared_users_manage: [ObjectId], }; } static scopes = [new ActiveScope] is_shared() { return this.shared_users_view.length > 0 || this.shared_users_update.length > 0 || this.shared_users_manage.length > 0 } // ================= RELATIONSHIPS ================= get user() { const User = this.models.get("auth:User") return this.belongs_to_one(User, "OrgUserId", "_id") } get nodes() { const Node = this.models.get("api:Node") return this.has_many(Node, "NodeIds", "UUID") } get childPages() { const Page = this.models.get("api:Page") return this.has_many(Page, "ChildPageIds", "UUID") } get parent() { const Parent = this.models.get("api:Page") return this.belongs_to_one(Parent, "ParentId", "UUID") } get view_users() { const User = this.models.get('auth:User') return this.has_many(User, 'shared_users_view', '_id') } get update_users() { const User = this.models.get('auth:User') return this.has_many(User, 'shared_users_update', '_id') } get manage_users() { const User = this.models.get('auth:User') return this.has_many(User, 'shared_users_manage', '_id') } // ================= SECURITY ================= accessible_by(user, mode = 'view') { const base_access = user.can(`page:${this.UUID}:${mode}`) } async is_accessible_by(user, mode = 'view') { const can_manage = user.can(`page:${this.UUID}:manage`) const can_update = user.can(`page:${this.UUID}:update`) const can_view = user.can(`page:${this.UUID}:view`) const can_all = user.can(`page:${this.UUID}`) // Allow universal access if ( can_all ) return true // deny if blocked else if ( user.can(`page:${this.UUID}:block`) ) return false // manage, update, view can view else if ( mode === 'view' && (can_manage || can_update || can_view) ) return true // manage, update can update else if ( mode === 'update' ) { if ( can_manage || can_update ) return true // If other permissions are explicitly set for this page, use those else if ( can_view ) return false } // manage can manage else if ( mode === 'manage' ) { if ( can_manage ) return true // If other permissions are explicitly set for this page, use those else if ( can_update || can_view ) return false } // allow universal access // deny blocked users // If there are no explicit permissions set for this node, check the parent const parent = await this.parent if ( parent ) return (await this.parent).is_accessible_by(user, mode) else return false } async access_level_for(user) { if ( await this.is_accessible_by(user, 'manage') ) return 'manage' else if ( await this.is_accessible_by(user, 'update') ) return 'update' else if ( await this.is_accessible_by(user, 'view') ) return 'view' else return false } async share_with(user, level = 'view') { if ( !['view', 'update', 'manage'].includes(level) ) { throw new Error(`Invalid share level: ${level}`) } // Remove existing sharing info await this.unshare_with(user) // Add the page to the user's permissions: user.allow(`page:${this.UUID}:${level}`) // Add the user to the appropriate access list this[`shared_users_${level}`].push(user._id) await this.save() await user.save() } async unshare_with(user) { // Remove this page from the user's permissions if ( user.can(`page:${this.UUID}`) ) user.disallow(`page:${this.UUID}`) for ( const level of ['view', 'update', 'manage'] ) { if ( user.can(`page:${this.UUID}:${level}`) ) user.disallow(`page:${this.UUID}:${level}`) } // Remove the user from this page's access lists this.shared_users_view = this.shared_users_view.filter(x => String(x) !== user.id) this.shared_users_update = this.shared_users_update.filter(x => String(x) !== user.id) this.shared_users_manage = this.shared_users_manage.filter(x => String(x) !== user.id) await this.save() await user.save() } } module.exports = exports = Page;