UNPKG

@syncable/core

Version:
335 lines 12.9 kB
"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