2020-02-08 09:54:48 +00:00
|
|
|
const Model = require("flitter-orm/src/model/Model");
|
|
|
|
const { ObjectId } = require("mongodb");
|
2020-02-08 12:57:38 +00:00
|
|
|
const uuid = require('uuid/v4');
|
2020-02-08 09:54:48 +00:00
|
|
|
|
2020-02-08 21:27:53 +00:00
|
|
|
const ActiveScope = require('../scopes/Active.scope')
|
|
|
|
|
2020-02-08 09:54:48 +00:00
|
|
|
/*
|
|
|
|
* 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 {
|
2020-02-08 12:57:38 +00:00
|
|
|
UUID: {type: String, default: () => uuid()},
|
2020-02-08 09:54:48 +00:00
|
|
|
Name: String,
|
2020-02-08 14:07:26 +00:00
|
|
|
OrgUserId: ObjectId,
|
2020-02-08 12:57:38 +00:00
|
|
|
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},
|
2020-02-08 21:27:53 +00:00
|
|
|
DeletedAt: Date,
|
|
|
|
Active: {type: Boolean, default: true},
|
2020-02-08 12:57:38 +00:00
|
|
|
CreatedUserId: {type: String},
|
|
|
|
UpdateUserId: {type: String},
|
|
|
|
ChildPageIds: [String],
|
2020-02-11 07:09:24 +00:00
|
|
|
|
2020-02-14 06:11:16 +00:00
|
|
|
// Menu flags
|
2020-02-11 07:09:24 +00:00
|
|
|
noDelete: { type: Boolean, default: false },
|
|
|
|
virtual: { type: Boolean, default: false },
|
2020-02-14 06:11:16 +00:00
|
|
|
|
|
|
|
// Sharing properties
|
|
|
|
shared_users_view: [ObjectId],
|
|
|
|
shared_users_update: [ObjectId],
|
|
|
|
shared_users_manage: [ObjectId],
|
2020-02-08 09:54:48 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-02-08 21:27:53 +00:00
|
|
|
static scopes = [new ActiveScope]
|
|
|
|
|
2020-03-01 21:37:52 +00:00
|
|
|
static async visible_by_user(user) {
|
|
|
|
const user_root = await user.get_root_page()
|
|
|
|
const user_root_children = await user_root.visible_flat_children(user)
|
|
|
|
|
|
|
|
const view_only_trees = await this.find({ shared_users_view: user._id })
|
|
|
|
let view_only_children = []
|
|
|
|
for ( const tree of view_only_trees ) {
|
|
|
|
if ( await tree.is_accessible_by(user) ) {
|
|
|
|
view_only_children.push(tree)
|
|
|
|
view_only_children = [...view_only_children, await tree.visible_flat_children(user)]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const update_trees = await this.find({ shared_users_update: user._id })
|
|
|
|
let update_children = []
|
|
|
|
for ( const tree of update_trees ) {
|
|
|
|
if ( await tree.is_accessible_by(user) ) {
|
|
|
|
update_children.push(tree)
|
|
|
|
update_children = [...update_children, await tree.visible_flat_children(user)]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const manage_trees = await this.find({ shared_users_manage: user._id })
|
|
|
|
let manage_children = []
|
|
|
|
for ( const tree of manage_trees ) {
|
|
|
|
if ( await tree.is_accessible_by(user) ) {
|
|
|
|
manage_children.push(tree)
|
|
|
|
manage_children = [...manage_children, await tree.visible_flat_children(user)]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const all_children = [...user_root_children, ...view_only_children, ...update_children, ...manage_children]
|
|
|
|
const unique_children = []
|
|
|
|
const seen_UUIDs = []
|
|
|
|
all_children.forEach(child => {
|
|
|
|
if ( !seen_UUIDs.includes(child.UUID) ) {
|
|
|
|
unique_children.push(child)
|
|
|
|
seen_UUIDs.push(child.UUID)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return unique_children
|
|
|
|
}
|
|
|
|
|
|
|
|
async visible_flat_children(user) {
|
|
|
|
const children = await this.childPages
|
|
|
|
let visible = []
|
|
|
|
if ( children ) {
|
|
|
|
for ( const child of children ) {
|
|
|
|
if ( !(await child.is_accessible_by(user)) ) continue
|
|
|
|
visible.push(child)
|
|
|
|
visible = [...visible, ...(await child.visible_flat_children(user))]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return visible
|
|
|
|
}
|
|
|
|
|
2020-02-14 06:11:16 +00:00
|
|
|
is_shared() {
|
|
|
|
return this.shared_users_view.length > 0 || this.shared_users_update.length > 0 || this.shared_users_manage.length > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// ================= RELATIONSHIPS =================
|
2020-02-08 09:54:48 +00:00
|
|
|
get user() {
|
2020-02-11 07:09:24 +00:00
|
|
|
const User = this.models.get("auth:User")
|
|
|
|
return this.belongs_to_one(User, "OrgUserId", "_id")
|
2020-02-08 09:54:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
get nodes() {
|
2020-02-11 07:09:24 +00:00
|
|
|
const Node = this.models.get("api:Node")
|
|
|
|
return this.has_many(Node, "NodeIds", "UUID")
|
2020-02-08 09:54:48 +00:00
|
|
|
}
|
|
|
|
get childPages() {
|
|
|
|
const Page = this.models.get("api:Page")
|
2020-02-08 14:07:26 +00:00
|
|
|
return this.has_many(Page, "ChildPageIds", "UUID")
|
2020-02-08 09:54:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
get parent() {
|
|
|
|
const Parent = this.models.get("api:Page")
|
2020-02-08 14:07:26 +00:00
|
|
|
return this.belongs_to_one(Parent, "ParentId", "UUID")
|
2020-02-08 09:54:48 +00:00
|
|
|
}
|
|
|
|
|
2020-02-14 06:11:16 +00:00
|
|
|
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 =================
|
2020-02-08 12:57:38 +00:00
|
|
|
accessible_by(user, mode = 'view') {
|
2020-02-14 06:11:16 +00:00
|
|
|
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()
|
2020-02-08 12:57:38 +00:00
|
|
|
}
|
|
|
|
|
2020-02-08 09:54:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = exports = Page;
|