@syncable/core
Version:
335 lines • 12.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const mobx_1 = require("mobx");
const access_control_1 = require("./access-control");
const access_control_rule_decorator_1 = require("./access-control-rule-decorator");
const syncable_1 = require("./syncable");
class SyncableObject {
constructor(refOrSyncable, container) {
this.container = container;
if ('_type' in refOrSyncable) {
this._syncable = refOrSyncable;
this._ref = syncable_1.getSyncableRef(refOrSyncable);
}
else {
this._ref = refOrSyncable;
}
}
get syncable() {
if (this._syncable) {
return this._syncable;
}
else {
let syncable = this.requiredContainer.getSyncable(this._ref);
if (!syncable) {
console.warn(`Syncable (${JSON.stringify(this._ref)}) no longer exists, you might have tried to access a syncable no longer available`, this);
return this._lastReferencedSyncable;
}
this._lastReferencedSyncable = syncable;
return syncable;
}
}
get id() {
return this.syncable._id;
}
get ref() {
return syncable_1.getSyncableRef(this.syncable);
}
get key() {
return syncable_1.getSyncableKey(this.syncable);
}
get createdAt() {
return new Date(this.syncable._createdAt);
}
get updatedAt() {
return new Date(this.syncable._updatedAt);
}
get requiredContainer() {
let container = this.container;
if (!container) {
throw new Error('The operation requires `manager` to present');
}
return container;
}
/**
* Override to specify.
*/
resolveRequisiteDependencyRefs(_changeType) {
return [];
}
/**
* Override to specify.
*/
resolveDependencyRefs() {
return [];
}
require(ref) {
return this.requiredContainer.requireSyncableObject(ref);
}
get(ref) {
return this.requiredContainer.getSyncableObject(ref);
}
getSyncableOverrides() {
return {};
}
getSanitizedFieldNames(context) {
let whitelistedFieldNameSet = new Set([
...this.getSecuringFieldNames(),
...this.getEssentialFieldNames(),
...access_control_1.SYNCABLE_ESSENTIAL_FIELD_NAMES,
]);
let { read: descriptor } = this.getAccessRightToAccessDescriptorDict(context);
return Object.keys(this.syncable).filter(fieldName => !whitelistedFieldNameSet.has(fieldName) &&
!testAccessDescriptor(descriptor, [fieldName]));
}
/**
* Override to specify.
*/
getSecuringFieldNames() {
return [];
}
/**
* Override to specify.
*/
getEssentialFieldNames() {
return [];
}
/**
* Override to specify.
*/
getDefaultACL() {
return [];
}
/**
* Get ACL with descending priorities
*/
getACL() {
let { _acl = [] } = this.syncable;
let defaultACL = this.getDefaultACL();
return lodash_1.default.sortBy(
// The original default ACL and ACL are written with ascending priorities
// (the later one overrides the former one), so we do a reverse here
// before the stable sort.
lodash_1.default.uniqBy([...defaultACL, ..._acl], entry => entry.name).reverse(), entry => -access_control_1.getAccessControlEntryPriority(entry));
}
getAccessRights(context, fieldNames) {
if (!fieldNames) {
return this.getObjectAccessRights(context);
}
let { _sanitizedFieldNames: sanitizedFieldNames } = this.syncable;
if (sanitizedFieldNames &&
lodash_1.default.intersection(fieldNames, sanitizedFieldNames).length > 0) {
return [];
}
let accessRightToAccessDescriptorDict = this.getAccessRightToAccessDescriptorDict(context, fieldNames);
return access_control_1.ACCESS_RIGHTS.filter(right => testAccessDescriptor(accessRightToAccessDescriptorDict[right], fieldNames));
}
testAccessRights(rights, context, fieldNames) {
let grantedRights = this.getAccessRights(context, fieldNames);
return lodash_1.default.difference(rights, grantedRights).length === 0;
}
validateAccessRights(rights, context, fieldNames) {
let grantedRights = this.getAccessRights(context, fieldNames);
if (lodash_1.default.difference(rights, grantedRights).length === 0) {
return;
}
throw new Error(`Granted access rights ${fieldNames ? `for fields [${fieldNames.join(', ')}]` : ''} (${grantedRights.join(', ')}) do not match requirements (${rights.join(', ')})`);
}
testBasic() {
return true;
}
getObjectAccessRights(context) {
let objectACL = this.getACL()
.filter(ace => !('fields' in ace))
.reverse();
if (!objectACL.length) {
return [...access_control_1.ACCESS_RIGHTS];
}
let grantedAccessRights = [];
for (let entry of objectACL) {
let { type, rights } = entry;
// Test whether the entry can mutate the granted rights to avoid
// unnecessary `testAccessControlEntry` calls
if (type === 'allow') {
if (lodash_1.default.difference(rights, grantedAccessRights).length === 0) {
continue;
}
}
else {
if (lodash_1.default.intersection(grantedAccessRights, rights).length === 0) {
continue;
}
}
if (!this.testAccessControlEntry(entry, context)) {
continue;
}
if (type === 'allow') {
grantedAccessRights = lodash_1.default.union(grantedAccessRights, rights);
}
else {
grantedAccessRights = lodash_1.default.difference(grantedAccessRights, rights);
}
}
return grantedAccessRights;
}
getAccessRightToAccessDescriptorDict(context, fieldNames) {
let objectAccessRights = this.getObjectAccessRights(context);
let accessRightToAccessDescriptorDict = {
read: objectAccessRights.includes('read') ? { allow: '*' } : { allow: [] },
write: objectAccessRights.includes('write') ? { allow: '*' } : { allow: [] },
full: objectAccessRights.includes('full') ? { allow: '*' } : { allow: [] },
};
let acl = this.getACL().reverse();
for (let entry of acl) {
let aceFieldNames;
if ('fields' in entry && entry.fields) {
aceFieldNames = fieldNames
? lodash_1.default.intersection(entry.fields, fieldNames)
: entry.fields;
if (!aceFieldNames.length) {
continue;
}
}
else {
aceFieldNames = '*';
}
let overrides = {};
let { type, rights } = entry;
for (let right of rights) {
let descriptor = accessRightToAccessDescriptorDict[right];
if (type === 'allow') {
// Allow
if ('allow' in descriptor) {
// Previously allow
if (descriptor.allow === '*') {
// Do nothing
}
else {
if (aceFieldNames === '*') {
overrides[right] = {
allow: '*',
};
}
else {
let updatedAllow = lodash_1.default.union(descriptor.allow, aceFieldNames);
if (updatedAllow.length > descriptor.allow.length) {
overrides[right] = {
allow: updatedAllow,
};
}
}
}
}
else {
// Previously deny
if (aceFieldNames === '*') {
overrides[right] = {
allow: '*',
};
}
else {
let updatedDeny = lodash_1.default.difference(descriptor.deny, aceFieldNames);
if (!updatedDeny.length) {
overrides[right] = {
allow: '*',
};
}
else if (updatedDeny.length < descriptor.deny.length) {
overrides[right] = {
deny: updatedDeny,
};
}
}
}
}
else {
// Deny
if ('allow' in descriptor) {
// Previously allow
if (descriptor.allow === '*') {
if (aceFieldNames === '*') {
overrides[right] = {
allow: [],
};
}
else {
overrides[right] = {
deny: aceFieldNames,
};
}
}
else {
if (aceFieldNames === '*') {
overrides[right] = {
allow: [],
};
}
else {
let updatedAllow = lodash_1.default.difference(descriptor.allow, aceFieldNames);
if (updatedAllow.length < descriptor.allow.length) {
overrides[right] = {
allow: updatedAllow,
};
}
}
}
}
else {
// Previously deny
if (aceFieldNames === '*') {
// Do nothing
}
else {
let updatedDeny = lodash_1.default.union(descriptor.deny, aceFieldNames);
if (updatedDeny.length > descriptor.deny.length) {
overrides[right] = {
deny: updatedDeny,
};
}
}
}
}
}
if (Object.keys(overrides).length === 0) {
continue;
}
if (this.testAccessControlEntry(entry, context)) {
Object.assign(accessRightToAccessDescriptorDict, overrides);
}
}
return accessRightToAccessDescriptorDict;
}
testAccessControlEntry(entry, context) {
let { rule: ruleName, options } = entry;
let rule = this.__accessControlRuleMap.get(ruleName);
if (!rule) {
throw new Error(`Unknown access control rule "${ruleName}"`);
}
return rule.test.call(this, context, options);
}
}
tslib_1.__decorate([
mobx_1.computed
], SyncableObject.prototype, "syncable", null);
tslib_1.__decorate([
mobx_1.computed
], SyncableObject.prototype, "createdAt", null);
tslib_1.__decorate([
mobx_1.computed
], SyncableObject.prototype, "updatedAt", null);
tslib_1.__decorate([
access_control_rule_decorator_1.AccessControlRule('basic')
], SyncableObject.prototype, "testBasic", null);
exports.AbstractSyncableObject = SyncableObject;
function testAccessDescriptor(descriptor, fieldNames) {
if ('allow' in descriptor) {
return (descriptor.allow === '*' ||
lodash_1.default.difference(fieldNames, descriptor.allow).length === 0);
}
else {
return lodash_1.default.intersection(descriptor.deny, fieldNames).length === 0;
}
}
//# sourceMappingURL=syncable-object.js.map