diff --git a/packages/livedata/livedata_server.js b/packages/livedata/livedata_server.js index 901dac065a..5465947415 100644 --- a/packages/livedata/livedata_server.js +++ b/packages/livedata/livedata_server.js @@ -599,7 +599,6 @@ _.extend(Meteor._LivedataSession.prototype, { fence.arm(); // we're done adding writes to the fence unblock(); // unblock, if the method hasn't done it already - exception = wrapInternalException( exception, "while invoking method '" + msg.method + "'"); diff --git a/packages/mongo-livedata/allow_tests.js b/packages/mongo-livedata/allow_tests.js index c37a273702..0e4da7fd57 100644 --- a/packages/mongo-livedata/allow_tests.js +++ b/packages/mongo-livedata/allow_tests.js @@ -8,8 +8,11 @@ _.each(['STRING', 'MONGO'], function (idGeneration) { // helper for defining a collection, subscribing to it, and defining // a method to clear it - var defineCollection = function(name, insecure) { - var collection = new Meteor.Collection(name + idGeneration, {idGeneration: idGeneration}); + var defineCollection = function(name, insecure, defaultFactory) { + var collection = new Meteor.Collection(name + idGeneration, { + idGeneration: idGeneration, + defaultFactory: defaultFactory + }); collection._insecure = insecure; if (Meteor.isServer) { @@ -57,7 +60,26 @@ _.each(['STRING', 'MONGO'], function (idGeneration) { var restrictedCollectionForFetchAllTest = defineCollection( "collection-restrictedForFetchAllTest", true /*insecure*/); + var restrictedCollectionWithFactory = defineCollection( + "withFactory", false, function (doc) { + return doc.a; + }); + restrictedCollectionWithFactory.allow({ + insert: function (userId, doc) { + return doc.foo === "foo"; + }, + update: function (userId, docs) { + return _.all(docs, function (doc) { + return doc.foo === "foo"; + }); + }, + remove: function (userId, docs) { + return _.all(docs, function (doc) { + return doc.bar === "bar"; + }); + } + }); // // Set up allow/deny rules for test collections // @@ -256,6 +278,8 @@ _.each(['STRING', 'MONGO'], function (idGeneration) { } if (Meteor.isClient) { + + // test that if allow is called once then the collection is // restricted, and that other mutations aren't allowed testAsyncMulti("collection - partial allow, " + idGeneration, [ @@ -319,6 +343,41 @@ _.each(['STRING', 'MONGO'], function (idGeneration) { } if (Meteor.isClient) { + var item1; + var item2; + testAsyncMulti("collection - restrected factories " + idGeneration, [ + function (test, expect) { + restrictedCollectionWithFactory.insert({ + a: {foo: "foo", bar: "bar", baz: "baz"} + }, expect(function (e, res) { + test.isFalse(e); + test.isTrue(res); + item1 = res; + })); + restrictedCollectionWithFactory.insert({ + a: {foo: "foo", bar: "quux", baz: "quux"}, + b: "potato" + }, expect(function (e, res) { + test.isFalse(e); + test.isTrue(res); + item2 = res; + })); + restrictedCollectionWithFactory.insert({ + a: {foo: "adsfadf", bar: "quux", baz: "quux"}, + b: "potato" + }, expect(function (e, res) { + test.isTrue(e); + })); + }, + function (test, expect) { + restrictedCollectionWithFactory.remove(item1, expect(function (e, res) { + test.isFalse(e); + })); + restrictedCollectionWithFactory.remove(item2, expect(function (e, res) { + test.isTrue(e); + })); + } + ]); testAsyncMulti("collection - insecure, " + idGeneration, [ function (test, expect) { insecureCollection.callClearMethod(test.runId(), expect(function () { diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index 4f2281c7a9..c2ae9588fc 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -557,6 +557,12 @@ Meteor.Collection.prototype._validatedInsert = function(userId, doc) { self._collection.insert.call(self._collection, doc); }; +var factoryAll = function (validator, docs) { + if (validator.factory) + return _.map(docs, validator.factory); + return docs; +}; + // Simulate a mongo `update` operation while validating that the access // control rules set by calls to `allow/deny` are satisfied. If all // pass, rewrite the mongo operation to use $in to set the list of @@ -585,7 +591,7 @@ Meteor.Collection.prototype._validatedUpdate = function( } }); - var findOptions = {}; + var findOptions = {factory: null}; if (!self._validators.fetchAllFields) { findOptions.fields = {}; _.each(self._validators.fetch, function(fieldName) { @@ -605,11 +611,15 @@ Meteor.Collection.prototype._validatedUpdate = function( docs = [doc]; } + var factoriedDocs; + // call user validators. // Any deny returns true means denied. if (_.any(self._validators.update.deny, function(validator) { + if (!factoriedDocs) + factoriedDocs = factoryAll(validator, docs); return validator(userId, - _.map(docs, _.partial(docToValidate, validator)), + factoriedDocs, fields, mutator); })) { @@ -617,8 +627,10 @@ Meteor.Collection.prototype._validatedUpdate = function( } // Any allow returns true means proceed. Throw error if they all fail. if (_.all(self._validators.update.allow, function(validator) { + if (!factoriedDocs) + factoriedDocs = factoryAll(validator, docs); return !validator(userId, - _.map(docs, _.partial(docToValidate, validator)), + factoriedDocs, fields, mutator); })) { @@ -656,7 +668,7 @@ Meteor.Collection.prototype._validatedUpdate = function( Meteor.Collection.prototype._validatedRemove = function(userId, selector) { var self = this; - var findOptions = {}; + var findOptions = {factory: null}; if (!self._validators.fetchAllFields) { findOptions.fields = {}; _.each(self._validators.fetch, function(fieldName) { @@ -671,13 +683,13 @@ Meteor.Collection.prototype._validatedRemove = function(userId, selector) { // call user validators. // Any deny returns true means denied. if (_.any(self._validators.remove.deny, function(validator) { - return validator(userId, docs); + return validator(userId, factoryAll(validator, docs)); })) { throw new Meteor.Error(403, "Access denied"); } // Any allow returns true means proceed. Throw error if they all fail. if (_.all(self._validators.remove.allow, function(validator) { - return !validator(userId, _.map(docs, _.partial(docToValidate, validator))); + return !validator(userId, factoryAll(validator, docs)); })) { throw new Meteor.Error(403, "Access denied"); }