From 586ea35071875234221d86bf6b09525709d36e78 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Fri, 7 Nov 2014 11:14:40 -0800 Subject: [PATCH] Allows ACLs/settings in model config --- lib/registry.js | 68 ++++++++++++++++++++++++++-- test/loopback.test.js | 103 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 5 deletions(-) diff --git a/lib/registry.js b/lib/registry.js index f940abfc..cf39879a 100644 --- a/lib/registry.js +++ b/lib/registry.js @@ -145,6 +145,25 @@ function buildModelOptionsFromConfig(config) { return options; } +/* + * Add the acl entry to the acls + * @param {Object[]} acls + * @param {Object} acl + */ +function addACL(acls, acl) { + for (var i = 0, n = acls.length; i < n; i++) { + // Check if there is a matching acl to be overriden + if (acls[i].property === acl.property && + acls[i].accessType === acl.accessType && + acls[i].principalType === acl.principalType && + acls[i].principalId === acl.principalId) { + acls[i] = acl; + return; + } + } + acls.push(acl); +} + /** * Alter an existing Model class. * @param {Model} ModelCtor The model constructor to alter. @@ -157,12 +176,51 @@ function buildModelOptionsFromConfig(config) { registry.configureModel = function(ModelCtor, config) { var settings = ModelCtor.settings; + var modelName = ModelCtor.modelName; - if (config.relations) { + // Relations + if (typeof config.relations === 'object' && config.relations !== null) { var relations = settings.relations = settings.relations || {}; Object.keys(config.relations).forEach(function(key) { + // FIXME: [rfeng] We probably should check if the relation exists relations[key] = extend(relations[key] || {}, config.relations[key]); }); + } else if (config.relations != null) { + console.warn('The relations property of `%s` configuration ' + + 'must be an object', modelName); + } + + // ACLs + if (Array.isArray(config.acls)) { + var acls = settings.acls = settings.acls || []; + config.acls.forEach(function(acl) { + addACL(acls, acl); + }); + } else if (config.acls != null) { + console.warn('The acls property of `%s` configuration ' + + 'must be an array of objects', modelName); + } + + // Settings + var excludedProperties = { + base: true, + 'super': true, + relations: true, + acls: true, + dataSource: true + }; + if (typeof config.options === 'object' && config.options !== null) { + for (var p in config.options) { + if (!(p in excludedProperties)) { + settings[p] = config.options[p]; + } else { + console.warn('Property `%s` cannot be reconfigured for `%s`', + p, modelName); + } + } + } else if (config.options != null) { + console.warn('The options property of `%s` configuration ' + + 'must be an object', modelName); } // It's important to attach the datasource after we have updated @@ -173,17 +231,17 @@ registry.configureModel = function(ModelCtor, config) { ': config.dataSource must be an instance of DataSource'); ModelCtor.attachTo(config.dataSource); debug('Attached model `%s` to dataSource `%s`', - ModelCtor.definition.name, config.dataSource.name); + modelName, config.dataSource.name); } else if (config.dataSource === null) { debug('Model `%s` is not attached to any DataSource by configuration.', - ModelCtor.definition.name); + modelName); } else { debug('Model `%s` is not attached to any DataSource, possibly by a mistake.', - ModelCtor.definition.name); + modelName); console.warn( 'The configuration of `%s` is missing `dataSource` property.\n' + 'Use `null` or `false` to mark models not attached to any data source.', - ModelCtor.definition.name); + modelName); } }; diff --git a/test/loopback.test.js b/test/loopback.test.js index 3fc4cc45..426dcc3a 100644 --- a/test/loopback.test.js +++ b/test/loopback.test.js @@ -249,6 +249,109 @@ describe('loopback', function() { expect(owner, 'model.prototype.owner').to.be.a('function'); expect(owner._targetClass).to.equal('User'); }); + + it('adds new acls', function() { + var model = loopback.Model.extend(uniqueModelName, {}, { + acls: [ + { + property: 'find', + accessType: 'EXECUTE', + principalType: 'ROLE', + principalId: '$everyone', + permission: 'DENY' + } + ] + }); + + loopback.configureModel(model, { + dataSource: null, + acls: [ + { + property: 'find', + accessType: 'EXECUTE', + principalType: 'ROLE', + principalId: 'admin', + permission: 'ALLOW' + } + ] + }); + + expect(model.settings.acls).eql([ + { + property: 'find', + accessType: 'EXECUTE', + principalType: 'ROLE', + principalId: '$everyone', + permission: 'DENY' + }, + { + property: 'find', + accessType: 'EXECUTE', + principalType: 'ROLE', + principalId: 'admin', + permission: 'ALLOW' + } + ]); + }); + + it('updates existing acls', function() { + var model = loopback.Model.extend(uniqueModelName, {}, { + acls: [ + { + property: 'find', + accessType: 'EXECUTE', + principalType: 'ROLE', + principalId: '$everyone', + permission: 'DENY' + } + ] + }); + + loopback.configureModel(model, { + dataSource: null, + acls: [ + { + property: 'find', + accessType: 'EXECUTE', + principalType: 'ROLE', + principalId: '$everyone', + permission: 'ALLOW' + } + ] + }); + + expect(model.settings.acls).eql([ + { + property: 'find', + accessType: 'EXECUTE', + principalType: 'ROLE', + principalId: '$everyone', + permission: 'ALLOW' + } + ]); + }); + + it('updates existing settings', function() { + var model = loopback.Model.extend(uniqueModelName, {}, { + ttl: 10, + emailVerificationRequired: false + }); + + loopback.configureModel(model, { + dataSource: null, + options: { + ttl: 20, + realmRequired: true, + base: 'X' + } + }); + + expect(model.settings).to.have.property('ttl', 20); + expect(model.settings).to.have.property('emailVerificationRequired', + false); + expect(model.settings).to.have.property('realmRequired', true); + expect(model.settings).to.not.have.property('base'); + }); }); describe('loopback object', function() {