Allow and deny rules with factories

This commit is contained in:
Naomi Seyfer
2013-03-09 18:03:10 -08:00
parent 2b4fb43bda
commit 00eedc6598
3 changed files with 79 additions and 9 deletions

View File

@@ -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 + "'");

View File

@@ -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 () {

View File

@@ -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");
}