@themost/data
Version:
MOST Web Framework Codename Blueshift - Data module
1,016 lines (997 loc) • 42.2 kB
JavaScript
// MOST Web Framework 2.0 Codename Blueshift BSD-3-Clause license Copyright (c) 2017-2022, THEMOST LP All rights reserved
/*eslint no-var: "off"*/
// noinspection ES6ConvertVarToLetConst
var { QueryEntity } = require('@themost/query');
var { QueryUtils } = require('@themost/query');
var async = require('async');
var { AccessDeniedError } = require('@themost/common');
var { DataConfigurationStrategy } = require('./data-configuration');
var _ = require('lodash');
var Q = require('q');
var { hasOwnProperty } = require('./has-own-property');
var { at } = require('lodash');
var { DataModelFilterParser } = require('./data-model-filter.parser');
/**
* @class
* @constructor
* @private
* @ignore
*/
function EachSeriesCancelled() {
//
}
/**
* @class
* @constructor
*/
function DataPermissionEventArgs() {
/**
* The target data model
* @type {DataModel}
*/
this.model = null;
/**
* The underlying query expression
* @type {QueryExpression}
*/
this.query = null;
/**
* The permission mask
* @type {Number}
*/
this.mask = null;
/**
* The query type
* @type {String}
*/
this.type = null;
/**
* The query type
* @type {String}
*/
this.privilege = null;
/**
* The data queryable object that emits the event.
* @type {DataQueryable|*}
*/
this.emitter = null;
}
/**
* An enumeration of the available permission masks
* @enum {number}
*/
function PermissionMask() {
}
/**
* Read Access Mask (1)
* @type {number}
*/
PermissionMask.Read = 1;
/**
* Create Access Mask (2)
* @type {number}
*/
PermissionMask.Create = 2;
/**
* Update Access Mask (4)
* @type {number}
*/
PermissionMask.Update = 4;
/**
* Delete Access Mask (8)
* @type {number}
*/
PermissionMask.Delete = 8;
/**
* Execute Access Mask (16)
* @type {number}
*/
PermissionMask.Execute = 16;
/**
* Full Access Mask (31)
* @type {number}
*/
PermissionMask.Owner = 31;
/**
* Splits a comma-separated or space-separated scope string e.g. "profile email" or "profile,email"
*
* Important note: https://www.rfc-editor.org/rfc/rfc6749#section-3.3 defines the regular expression of access token scopes
* which is a space separated string. Several OAuth2 servers use a comma-separated list instead.
*
* The operation will try to use both implementations by excluding comma ',' from access token regular expressions
* @param {string} str
* @returns {Array<string>}
*/
function splitScope(str) {
// the default regular expression includes comma /([\x21\x23-\x5B\x5D-\x7E]+)/g
// the modified regular expression excludes comma /x2C /([\x21\x23-\x2B\x2D-\x5B\x5D-\x7E]+)/g
var re = /([\x21\x23-\x2B\x2D-\x5B\x5D-\x7E]+)/g
var results = [];
var match = re.exec(str);
while(match !== null) {
results.push(match[0]);
match = re.exec(str);
}
return results;
}
/**
*
* @param {import("./data-model").DataModel} model
*/
function DataPermissionExclusion(model) {
this.model = model;
}
/**
* @param {import("./types").DataModelPrivilege} privilege
* @param {function(Error=,boolean=)} callback
*/
DataPermissionExclusion.prototype.shouldExclude = function (privilege, callback) {
if (privilege == null) {
return callback(new Error('Data model privilege may not be null'));
}
if (privilege.exclude == null) {
return callback();
}
if (typeof privilege.exclude !== 'string') {
return callback(new TypeError('Exclude expression must be a string'));
}
if (privilege.exclude.trim().length === 0) {
return callback();
}
var context = this.model.context;
var users = context.model('User');
var parser = new DataModelFilterParser(users);
var username = (context.user && context.user.name) || 'anonymous';
var addSelect = [
{
name: {
$value: username
}
}
];
parser.resolvingMember.subscribe(function (event) {
var propertyPath = event.member.split('/');
if (propertyPath[0] === 'context') {
propertyPath.splice(0, 1);
var property = at(context, propertyPath.join('.'))[0];
var propertyName = propertyPath[propertyPath.length - 1];
var exists = addSelect.findIndex((item) => Object.prototype.hasOwnProperty.call(item, propertyPath));
if (exists < 0) {
var select = {};
Object.defineProperty(select, propertyName, {
enumerable: true,
configurable: true,
value: {
$value: property
}
});
addSelect.push(select);
}
event.result = {
$select: propertyName
}
}
});
void parser.parseAsync(privilege.exclude).then(function (q) {
var q1 = users.asQueryable();
q1.query.select([].concat(addSelect));
Object.assign(q1.query, {
$expand: q.$expand,
$where: q.$where
});
q1.query.prepare().where('name').equal(username);
void context.db.execute(q1.query, [], function (err, result) {
if (err) {
return callback(err);
}
return callback(null, result.length > 0);
});
}).catch(function (err) {
return callback(err);
});
};
/**
* @param {import("./types").DataModelPrivilege} privilege
* @param {function(Error=,boolean=)} callback
*/
DataPermissionExclusion.prototype.shouldExcludeAsync = function (privilege) {
var self = this;
return new Promise(function(resolve, reject) {
void self.shouldExclude(privilege, function(err, result) {
if (err) {
return reject(err);
}
return resolve(result);
});
});
};
/**
*
* @param {import("./types").DataModelPrivilege} privilege
*/
DataPermissionExclusion.prototype.tryExclude = function(privilege) {
var context = this.model.context;
if (privilege == null) {
throw new Error('Privilege may not be null');
}
if (privilege.scope == null) {
return false;
}
if (Array.isArray(privilege.scope) === false) {
throw new TypeError('Privilege scope must be an array');
}
// get context scopes
const authenticationScope = context.user && context.user.authenticationScope;
if (authenticationScope == null) {
return false;
}
// get context scopes as array e.g. "profile", "email", "sales"
var scopes = [];
if (typeof authenticationScope === 'string') {
// check for space separated
scopes = splitScope(authenticationScope);
} else if (Array.isArray(authenticationScope)) {
scopes = authenticationScope.slice();
}
// search privilege scopes e.g. "sales", "orders"
var find = privilege.scope.find(function(scope) {
return scopes.includes(scope);
});
// if there is no scope defined, privilege should be excluded
return find == null;
}
/**
* @class
* @constructor
*/
function DataPermissionEventListener() {
//
}
/**
* Occurs before creating or updating a data object.
* @param {DataEventArgs} event - An object that represents the event arguments passed to this operation.
* @param {Function} callback - A callback function that should be called at the end of this operation. The first argument may be an error if any occurred.
*/
DataPermissionEventListener.prototype.beforeSave = function (event, callback) {
DataPermissionEventListener.prototype.validate(event, callback);
};
/**
* Occurs before removing a data object.
* @param {DataEventArgs} event - An object that represents the event arguments passed to this operation.
* @param {Function} callback - A callback function that should be called at the end of this operation. The first argument may be an error if any occurred.
* @returns {DataEventListener}
*/
DataPermissionEventListener.prototype.beforeRemove = function (event, callback) {
DataPermissionEventListener.prototype.validate(event, callback);
};
/**
* Validates permissions against the event arguments provided.
* @param {DataEventArgs|DataPermissionEventArgs} event - An object that represents the event arguments passed to this operation.
* @param {Function} callback - A callback function that should be called at the end of this operation. The first argument may be an error if any occurred.
*/
DataPermissionEventListener.prototype.validate = function (event, callback) {
var model = event.model;
/**
* @type {DataContext|*}
*/
var context = event.model.context;
var requestMask = 1;
var workspace = 1;
//ensure silent operation
if (event.model && event.model.$silent) {
return callback();
}
if (event.state === 0)
requestMask = PermissionMask.Read;
else if (event.state === 1)
requestMask = PermissionMask.Create;
else if (event.state === 2)
requestMask = PermissionMask.Update;
else if (event.state === 4)
requestMask = PermissionMask.Delete;
else if (event.state === 16)
requestMask = PermissionMask.Execute;
else {
if (event.mask) {
// use mask defined by event arguments
requestMask = event.mask;
}
else {
// throw error
return callback(new Error('Target object has an invalid state.'));
}
}
// use privilege defined by event arguments
var privilege = model.name;
var parentPrivilege = null;
if (event.privilege) {
privilege = event.privilege;
}
//validate throwError
if (typeof event.throwError === 'undefined')
event.throwError = true;
context.user = context.user || { name: 'anonymous', authenticationType: 'None' };
//description: Use unattended execution account as an escape permission check account
var authSettings = context.getConfiguration().getStrategy(DataConfigurationStrategy).getAuthSettings();
if (authSettings) {
var unattendedExecutionAccount = authSettings.unattendedExecutionAccount;
if ((typeof unattendedExecutionAccount !== 'undefined'
|| unattendedExecutionAccount != null)
&& (unattendedExecutionAccount === context.user.name)) {
event.result = true;
return callback();
}
}
//get user key
var users = context.model('User');
if (users == null) {
//do nothing
return callback();
}
var permissions = context.model('Permission');
if (permissions == null) {
//do nothing
return callback();
}
DataPermissionEventListener.prototype.effectiveAccounts(context, function (err, accounts) {
if (err) {
return callback(err);
}
var permEnabled = model.privileges.filter(function (x) { return !x.disabled; }, model.privileges).length > 0;
//get all enabled privileges
var privileges = model.privileges.filter(function (x) { return !x.disabled && ((x.mask & requestMask) === requestMask) });
if (privileges.length === 0) {
if (event.throwError) {
//if the target model has privileges but it has no privileges with the requested mask
if (permEnabled) {
//throw error
var error = new Error('Access denied.');
error.statusCode = 401;
return callback(error);
}
else {
//do nothing
return callback();
}
}
else {
//set result to false (or true if model has no privileges at all)
event.result = !permEnabled;
//and exit
return callback();
}
}
else {
var cancel = false;
event.result = false;
//enumerate privileges
async.eachSeries(privileges, function (item, cb) {
if (cancel) {
return cb();
}
try {
var exclude = new DataPermissionExclusion(model).tryExclude(item);
if (exclude) {
return cb();
}
//global
if (item.type === 'global') {
if (typeof item.account !== 'undefined') {
//check if a privilege is assigned by the model
if (item.account === '*') {
//get permission and exit
cancel = true;
event.result = true;
return cb();
}
else if (hasOwnProperty(item, 'account')) {
if (accounts.findIndex(function (x) { return x.name === item.account }) >= 0) {
cancel = true;
event.result = true;
return cb();
}
}
}
//try to find user has global permissions assigned
permissions.where('privilege').equal(privilege)
.and('parentPrivilege').equal(null)
.and('target').equal('0')
.and('workspace').equal(workspace)
.and('account').in(accounts.map(function (x) { return x.id; }))
.and('mask').bit(requestMask, requestMask).silent().count(function (err, count) {
if (err) {
return cb(err);
}
else {
if (count >= 1) {
cancel = true;
event.result = true;
}
return cb();
}
});
}
else if (item.type === 'parent') {
var mapping = model.inferMapping(item.property);
if (!mapping) {
return cb();
}
// validate parent association
if (mapping.childModel !== model.name) {
return cb(new Error('Parent privileges are assigned by a wrong type of association.'))
}
// use parentPrivilege provided by arguments
parentPrivilege = mapping.parentModel;
if (event.parentPrivilege) {
parentPrivilege = event.parentPrivilege;
}
if (requestMask === PermissionMask.Create) {
permissions.where('privilege').equal(privilege)
.and('parentPrivilege').equal(parentPrivilege)
.and('target').equal(event.target[mapping.childField])
.and('workspace').equal(workspace)
.and('account').in(accounts.map(function (x) { return x.id; }))
.and('mask').bit(requestMask, requestMask).silent().count(function (err, count) {
if (err) {
return cb(err);
}
else {
if (count >= 1) {
cancel = true;
event.result = true;
}
return cb();
}
});
}
else {
//get original value
model.where(model.primaryKey).equal(event.target[model.primaryKey]).select(mapping.childField).first(function (err, result) {
if (err) {
cb(err);
}
else if (result) {
permissions.where('privilege').equal(privilege)
.and('parentPrivilege').equal(parentPrivilege)
.and('target').equal(result[mapping.childField])
.and('workspace').equal(workspace)
.and('account').in(accounts.map(function (x) { return x.id; }))
.and('mask').bit(requestMask, requestMask).silent().count(function (err, count) {
if (err) {
return cb(err);
}
else {
if (count >= 1) {
cancel = true;
event.result = true;
}
return cb();
}
});
}
else {
return cb();
}
});
}
}
else if (item.type === 'item') {
//if target object is a new object
if (requestMask === PermissionMask.Create) {
//do nothing
return cb();
}
permissions.where('privilege').equal(privilege)
.and('parentPrivilege').equal(null)
.and('target').equal(event.target[model.primaryKey])
.and('workspace').equal(workspace)
.and('account').in(accounts.map(function (x) { return x.id; }))
.and('mask').bit(requestMask, requestMask).silent().count(function (err, count) {
if (err) {
return cb(err);
}
else {
if (count >= 1) {
cancel = true;
event.result = true;
}
return cb();
}
});
}
else if (item.type === 'self') {
// check if the specified privilege has account attribute
if (typeof item.account !== 'undefined' && item.account !== null && item.account !== '*') {
// if user does not have this account return
if (accounts.findIndex(function (x) { return x.name === item.account; }) < 0) {
return cb();
}
}
// validate permission exclusion
new DataPermissionExclusion(model).shouldExclude(item, function(err, exclude) {
if (err) {
return cb(err);
}
// if privilege should be excluded
if (exclude === true) {
// break
return cb;
}
// otherwise, continue with permission validation
if (requestMask === PermissionMask.Create) {
var query = QueryUtils.query(model.viewAdapter);
var fields = [], field;
//cast target
var name, obj = event.target;
model.attributes.forEach(function (x) {
name = hasOwnProperty(obj, x.property) ? x.property : x.name;
if (hasOwnProperty(obj, name)) {
var mapping = model.inferMapping(name);
if (_.isNil(mapping)) {
field = {};
field[x.name] = { $value: obj[name] };
fields.push(field);
}
else if ((mapping.associationType === 'association') && (mapping.childModel === model.name)) {
if (typeof obj[name] === 'object' && obj[name] !== null) {
//set associated key value (event.g. primary key value)
field = {};
field[x.name] = { $value: obj[name][mapping.parentField] };
fields.push(field);
}
else {
//set raw value
field = {};
field[x.name] = { $value: obj[name] };
fields.push(field);
}
}
}
});
//add fields
query.select(fields);
//set fixed query
query.$fixed = true;
model.filter(item.filter, function (err, q) {
if (err) {
cb(err);
}
else {
//set where from DataQueryable.query
query.$where = q.query.$prepared;
query.$expand = q.query.$expand;
model.context.db.execute(query, null, function (err, result) {
if (err) {
return cb(err);
}
else {
if (result.length === 1) {
cancel = true;
event.result = true;
}
return cb();
}
});
}
});
} else {
//get privilege filter
model.filter(item.filter, function (err, q) {
if (err) {
return cb(err);
}
else {
//prepare query and append primary key expression
q.where(model.primaryKey).equal(event.target[model.primaryKey]).silent().count(function (err, count) {
if (err) { cb(err); return; }
if (count >= 1) {
cancel = true;
event.result = true;
}
return cb();
})
}
});
}
});
}
else {
//do nothing (unknown permission)
return cb();
}
} catch (error) {
return cb(error);
}
}, function (err) {
// return error
if (err) {
return callback(err);
}
// if user has access
if (event.result === true) {
return callback();
}
// if process should throw an error
if (event.throwError) {
// throw an access denied error
return callback(Object.assign(new AccessDeniedError(), {
model: model.name
}));
}
// otherwise, return result
return callback(null, event.result);
});
}
});
};
/**
* @private
* @type {string}
*/
var ANONYMOUS_USER_CACHE_PATH = '/User/anonymous';
/**
* @param {DataContext} context
* @param {function(Error=,*=)} callback
* @private
*/
function anonymousUser(context, callback) {
queryUser(context, 'anonymous', function (err, result) {
if (err) {
callback(err);
}
else {
callback(null, result || { id: null, name: 'anonymous', groups: [], enabled: false });
}
});
}
/**
*
* @param {DataContext} context
* @param {string} username
* @param {function(Error=,*=)} callback
* @private
*/
function queryUser(context, username, callback) {
try {
if (_.isNil(context)) {
return callback();
}
var users = context.model('User');
if (_.isNil(users)) {
return callback();
}
users.where('name').equal(username).silent().select('id', 'name').expand('groups').getTypedItem().then(function (result) {
return callback(null, result);
}).catch(function (err) {
return callback(err);
});
}
catch (err) {
callback(err);
}
}
DataPermissionEventListener.prototype.effectiveAccounts = function (context, callback) {
var accounts = [ { id: null } ];
if (context == null) {
//push empty accounts
return callback(null, accounts);
}
// validate context user
if (context.user == null) {
context.setUser({ name:'anonymous',authenticationType:'None' });
}
try {
var invokeGetUser = context.user.name === 'anonymous' ? context.getAnonymousUser : context.getUser;
void invokeGetUser.call(context).then(function(user) {
if (user) {
accounts = [
{ id: user.id, name: user.name }
];
if (Array.isArray(user.groups)) {
accounts.push.apply(accounts, user.groups.map(function(x) { return { id: x.id, name: x.name }; }));
}
}
return callback(null, accounts);
}).catch(function (err) {
return callback(err);
});
} catch (err) {
return callback(err);
}
}
/**
* Occurs before executing a data operation.
* @param {DataEventArgs} event - An object that represents the event arguments passed to this operation.
* @param {Function} callback - A callback function that should be called at the end of this operation. The first argument may be an error if any occurred.
*/
DataPermissionEventListener.prototype.beforeExecute = function (event, callback) {
if (_.isNil(event.model)) {
return callback();
}
//ensure silent query operation
if (event.emitter && event.emitter.$silent) {
return callback();
}
var model = event.model;
/**
* @type {DataContext|*}
*/
var context = event.model.context;
var requestMask = 1;
var workspace = 1;
// eslint-disable-next-line no-unused-vars
var privilege = model.name;
// eslint-disable-next-line no-unused-vars
var parentPrivilege = null;
//get privilege from event arguments if it's defined (event.g. the operation requests execute permission User.ChangePassword where
// privilege=ChangePassword and parentPrivilege=User)
if (event.privilege) {
//event argument is the privilege
privilege = event.privilege;
//and model is the parent privilege
parentPrivilege = model.name;
}
//do not check permissions if the target model has no privileges defined
if (model.privileges.filter(function (x) { return !x.disabled; }, model.privileges).length === 0) {
return callback(null);
}
//infer permission mask
if (typeof event.mask !== 'undefined') {
requestMask = event.mask;
}
else {
if (event.query) {
//infer mask from query type
if (event.query.$select)
//read permissions
requestMask = 1;
else if (event.query.$insert)
//create permissions
requestMask = 2;
else if (event.query.$update)
//update permissions
requestMask = 4;
else if (event.query.$delete)
//delete permissions
requestMask = 8;
}
}
//ensure context user
context.user = context.user || { name: 'anonymous', authenticationType: 'None' };
//change: 2-May 2015
//description: Use unattended execution account as an escape permission check account
var authSettings = context.getConfiguration().getStrategy(DataConfigurationStrategy).getAuthSettings();
if (authSettings) {
var unattendedExecutionAccount = authSettings.unattendedExecutionAccount;
if ((typeof unattendedExecutionAccount !== 'undefined'
|| unattendedExecutionAccount !== null)
&& (unattendedExecutionAccount === context.user.name)) {
return callback();
}
}
if (event.query) {
//get user key
var users = context.model('User');
var permissions = context.model('Permission');
if (users == null) {
//do nothing
return callback();
}
if (permissions == null) {
//do nothing
return callback();
}
//get model privileges (and clone them)
var modelPrivileges = _.cloneDeep(model.privileges || []);
// if there are no privileges
if (modelPrivileges.length === 0) {
// add defaults
modelPrivileges.push.apply(modelPrivileges, [
{
type: 'global',
mask: 31 // read, insert, update, delete and execute
}
]);
}
// validate current emitter view
if (event.emitter && event.emitter.$view) {
// get array
const viewPrivileges = event.emitter.$view.privileges || [];
if (viewPrivileges.length) {
// initialize privileges
modelPrivileges = [
{
type: 'global',
mask: 31 // read, insert, update, delete and execute
}
];
// set parent privilege e.g. Order
parentPrivilege = model.name;
// set privilege e.g. Delivered
privilege = event.emitter.$view.name;
// and append view privileges
modelPrivileges.push.apply(modelPrivileges, viewPrivileges);
}
}
//if model has no privileges defined
if (modelPrivileges.length === 0) {
//do nothing and exit
return callback();
}
//tuning up operation
//validate request mask permissions against all users privilege { mask:<requestMask>,disabled:false,account:"*" }
var allUsersPrivilege = modelPrivileges.find(function (x) {
return (((x.mask & requestMask) === requestMask) && !x.disabled && (x.account === '*'));
});
if (typeof allUsersPrivilege !== 'undefined') {
//do nothing
return callback();
}
DataPermissionEventListener.prototype.effectiveAccounts(context, function (err, accounts) {
if (err) { callback(err); return; }
//get all enabled privileges
var privileges = modelPrivileges.filter(function (x) {
return !x.disabled && ((x.mask & requestMask) === requestMask);
});
// set query lastIndex
event.query.$lastIndex = parseInt(event.query.$lastIndex, 10) || 0;
var cancel = false, assigned = false, entity = new QueryEntity(model.viewAdapter), expand = null,
perms1 = new QueryEntity(permissions.viewAdapter).as(permissions.viewAdapter + event.query.$lastIndex.toString()), expr = null;
async.eachSeries(privileges, function (item, cb) {
if (assigned) {
return cb();
}
try {
var exclude = new DataPermissionExclusion(model).tryExclude(item);
if (exclude) {
return cb();
}
if (item.type === 'global') {
//check if a privilege is assigned by the model
if (item.account === '*') {
//get permission and exit
assigned = true;
return cb(new EachSeriesCancelled());
}
else if (hasOwnProperty(item, 'account')) {
if (accounts.findIndex(function (x) { return x.name === item.account }) >= 0) {
assigned = true;
return cb(new EachSeriesCancelled());
}
}
//try to find user has global permissions assigned
permissions.where('privilege').equal(privilege).
and('parentPrivilege').equal(parentPrivilege).
and('target').equal('0').
and('workspace').equal(1).
and('account').in(accounts.map(function (x) { return x.id; })).
and('mask').bit(requestMask, requestMask).silent().count(function (err, count) {
if (err) {
cb(err);
}
else {
if (count >= 1) {
assigned = true;
return cb(new EachSeriesCancelled());
}
cb();
}
});
}
else if (item.type === 'parent') {
//get field mapping
var mapping = model.inferMapping(item.property);
if (!mapping) {
return cb();
}
if (_.isNil(expr))
expr = QueryUtils.query();
expr.where(entity.select(mapping.childField)).equal(perms1.select('target')).
and(perms1.select('privilege')).equal(mapping.childModel).
and(perms1.select('parentPrivilege')).equal(mapping.parentModel).
and(perms1.select('workspace')).equal(workspace).
and(perms1.select('mask')).bit(requestMask, requestMask).
and(perms1.select('account')).in(accounts.map(function (x) { return x.id; })).prepare(true);
assigned = true;
cb();
}
else if (item.type === 'item') {
if (_.isNil(expr))
expr = QueryUtils.query();
expr.where(entity.select(model.primaryKey)).equal(perms1.select('target')).
and(perms1.select('privilege')).equal(model.name).
and(perms1.select('parentPrivilege')).equal(null).
and(perms1.select('workspace')).equal(workspace).
and(perms1.select('mask')).bit(requestMask, requestMask).
and(perms1.select('account')).in(accounts.map(function (x) { return x.id; })).prepare(true);
assigned = true;
cb();
}
else if (item.type === 'self') {
// check if the specified privilege has account attribute
if (typeof item.account !== 'undefined' && item.account !== null && item.account !== '*') {
// if user does not have this account return
if (accounts.findIndex(function (x) { return x.name === item.account; }) < 0) {
return cb();
}
}
if (typeof item.filter === 'string') {
model.filter(item.filter, function (err, q) {
if (err) {
return cb(err);
}
else {
if (q.query.$prepared) {
if (_.isNil(expr))
expr = QueryUtils.query();
expr.$where = q.query.$prepared;
if (q.query.$expand) {
// combine expands
expand = expand || [];
expand.push.apply(expand, q.query.$expand);
}
expr.prepare(true);
assigned = true;
return cb();
}
else
return cb();
}
});
} else {
return cb();
}
}
else {
cb();
}
}
catch (e) {
cb(e);
}
}, function (err) {
if (err) {
cancel = (err instanceof EachSeriesCancelled);
if (!cancel) {
return callback(err);
}
}
if (!assigned) {
//prepare no access query
event.query.prepare();
//add no record parameter
event.query.where(event.model.fieldOf(event.model.primaryKey)).equal(null).prepare();
return callback();
}
else if (expr) {
return context.model('Permission').migrate(function (err) {
if (err) { return callback(err); }
var q = QueryUtils.query(model.viewAdapter).select([model.primaryKey]).distinct();
if (expand) {
var arrExpand = [].concat(expand);
_.forEach(arrExpand, function (x) {
q.join(x.$entity).with(x.$with);
});
}
q.join(perms1).with(expr);
// set static alias
event.query.$lastIndex += 1;
var pqAlias = context.model('Permission').viewAdapter + event.query.$lastIndex.toString();
event.query.join(q.as(pqAlias)).with(QueryUtils.where(entity.select(model.primaryKey)).equal(new QueryEntity(pqAlias).select(model.primaryKey)));
return callback();
});
}
return callback();
});
});
}
else {
return callback();
}
};
module.exports = {
DataPermissionEventArgs,
DataPermissionEventListener,
DataPermissionExclusion,
PermissionMask
};