mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Previously, the document would be transformed based on the first deny or allow rule's transform, instead of using the matching transform rule. This bug only applied to update, not insert or remove. Technically one could do something fancy to avoid calling transform unnecessarily if there are many validators with the same transform, but if you're stressing that much about performance you might as well just write a method instead of using allow/deny. Fixes #3108.
878 lines
32 KiB
JavaScript
878 lines
32 KiB
JavaScript
if (Meteor.isServer) {
|
|
// Set up allow/deny rules for test collections
|
|
|
|
var allowCollections = {};
|
|
|
|
// We create the collections in the publisher (instead of using a method or
|
|
// something) because if we made them with a method, we'd need to follow the
|
|
// method with some subscribes, and it's possible that the method call would
|
|
// be delayed by a wait method and the subscribe messages would be sent before
|
|
// it and fail due to the collection not yet existing. So we are very hacky
|
|
// and use a publish.
|
|
Meteor.publish("allowTests", function (nonce, idGeneration) {
|
|
check(nonce, String);
|
|
check(idGeneration, String);
|
|
var cursors = [];
|
|
var needToConfigure = undefined;
|
|
|
|
// helper for defining a collection. we are careful to create just one
|
|
// Mongo.Collection even if the sub body is rerun, by caching them.
|
|
var defineCollection = function(name, insecure, transform) {
|
|
var fullName = name + idGeneration + nonce;
|
|
|
|
var collection;
|
|
if (_.has(allowCollections, fullName)) {
|
|
collection = allowCollections[fullName];
|
|
if (needToConfigure === true)
|
|
throw new Error("collections inconsistently exist");
|
|
needToConfigure = false;
|
|
} else {
|
|
collection = new Mongo.Collection(
|
|
fullName, {idGeneration: idGeneration, transform: transform});
|
|
allowCollections[fullName] = collection;
|
|
if (needToConfigure === false)
|
|
throw new Error("collections inconsistently don't exist");
|
|
needToConfigure = true;
|
|
collection._insecure = insecure;
|
|
var m = {};
|
|
m["clear-collection-" + fullName] = function() {
|
|
collection.remove({});
|
|
};
|
|
Meteor.methods(m);
|
|
}
|
|
|
|
cursors.push(collection.find());
|
|
return collection;
|
|
};
|
|
|
|
var insecureCollection = defineCollection(
|
|
"collection-insecure", true /*insecure*/);
|
|
// totally locked down collection
|
|
var lockedDownCollection = defineCollection(
|
|
"collection-locked-down", false /*insecure*/);
|
|
// restricted collection with same allowed modifications, both with and
|
|
// without the `insecure` package
|
|
var restrictedCollectionDefaultSecure = defineCollection(
|
|
"collection-restrictedDefaultSecure", false /*insecure*/);
|
|
var restrictedCollectionDefaultInsecure = defineCollection(
|
|
"collection-restrictedDefaultInsecure", true /*insecure*/);
|
|
var restrictedCollectionForUpdateOptionsTest = defineCollection(
|
|
"collection-restrictedForUpdateOptionsTest", true /*insecure*/);
|
|
var restrictedCollectionForPartialAllowTest = defineCollection(
|
|
"collection-restrictedForPartialAllowTest", true /*insecure*/);
|
|
var restrictedCollectionForPartialDenyTest = defineCollection(
|
|
"collection-restrictedForPartialDenyTest", true /*insecure*/);
|
|
var restrictedCollectionForFetchTest = defineCollection(
|
|
"collection-restrictedForFetchTest", true /*insecure*/);
|
|
var restrictedCollectionForFetchAllTest = defineCollection(
|
|
"collection-restrictedForFetchAllTest", true /*insecure*/);
|
|
var restrictedCollectionWithTransform = defineCollection(
|
|
"withTransform", false, function (doc) {
|
|
return doc.a;
|
|
});
|
|
var restrictedCollectionForInvalidTransformTest = defineCollection(
|
|
"collection-restrictedForInvalidTransform", false /*insecure*/);
|
|
var restrictedCollectionForClientIdTest = defineCollection(
|
|
"collection-restrictedForClientIdTest", false /*insecure*/);
|
|
|
|
if (needToConfigure) {
|
|
restrictedCollectionWithTransform.allow({
|
|
insert: function (userId, doc) {
|
|
return doc.foo === "foo";
|
|
},
|
|
update: function (userId, doc) {
|
|
return doc.foo === "foo";
|
|
},
|
|
remove: function (userId, doc) {
|
|
return doc.bar === "bar";
|
|
}
|
|
});
|
|
restrictedCollectionWithTransform.allow({
|
|
// transform: null means that doc here is the top level, not the 'a'
|
|
// element.
|
|
transform: null,
|
|
insert: function (userId, doc) {
|
|
return !!doc.topLevelField;
|
|
},
|
|
update: function (userId, doc) {
|
|
return !!doc.topLevelField;
|
|
}
|
|
});
|
|
restrictedCollectionForInvalidTransformTest.allow({
|
|
// transform must return an object which is not a mongo id
|
|
transform: function (doc) { return doc._id; },
|
|
insert: function () { return true; }
|
|
});
|
|
restrictedCollectionForClientIdTest.allow({
|
|
// This test just requires the collection to trigger the restricted
|
|
// case.
|
|
insert: function () { return true; }
|
|
});
|
|
|
|
// two calls to allow to verify that either validator is sufficient.
|
|
var allows = [{
|
|
insert: function(userId, doc) {
|
|
return doc.canInsert;
|
|
},
|
|
update: function(userId, doc) {
|
|
return doc.canUpdate;
|
|
},
|
|
remove: function (userId, doc) {
|
|
return doc.canRemove;
|
|
}
|
|
}, {
|
|
insert: function(userId, doc) {
|
|
return doc.canInsert2;
|
|
},
|
|
update: function(userId, doc, fields, modifier) {
|
|
return -1 !== _.indexOf(fields, 'canUpdate2');
|
|
},
|
|
remove: function(userId, doc) {
|
|
return doc.canRemove2;
|
|
}
|
|
}];
|
|
|
|
// two calls to deny to verify that either one blocks the change.
|
|
var denies = [{
|
|
insert: function(userId, doc) {
|
|
return doc.cantInsert;
|
|
},
|
|
remove: function (userId, doc) {
|
|
return doc.cantRemove;
|
|
}
|
|
}, {
|
|
insert: function(userId, doc) {
|
|
// Don't allow explicit ID to be set by the client.
|
|
return _.has(doc, '_id');
|
|
},
|
|
update: function(userId, doc, fields, modifier) {
|
|
return -1 !== _.indexOf(fields, 'verySecret');
|
|
}
|
|
}];
|
|
|
|
_.each([
|
|
restrictedCollectionDefaultSecure,
|
|
restrictedCollectionDefaultInsecure,
|
|
restrictedCollectionForUpdateOptionsTest
|
|
], function (collection) {
|
|
_.each(allows, function (allow) {
|
|
collection.allow(allow);
|
|
});
|
|
_.each(denies, function (deny) {
|
|
collection.deny(deny);
|
|
});
|
|
});
|
|
|
|
// just restrict one operation so that we can verify that others
|
|
// fail
|
|
restrictedCollectionForPartialAllowTest.allow({
|
|
insert: function() {}
|
|
});
|
|
restrictedCollectionForPartialDenyTest.deny({
|
|
insert: function() {}
|
|
});
|
|
|
|
// verify that we only fetch the fields specified - we should
|
|
// be fetching just field1, field2, and field3.
|
|
restrictedCollectionForFetchTest.allow({
|
|
insert: function() { return true; },
|
|
update: function(userId, doc) {
|
|
// throw fields in doc so that we can inspect them in test
|
|
throw new Meteor.Error(
|
|
999, "Test: Fields in doc: " + _.keys(doc).join(','));
|
|
},
|
|
remove: function(userId, doc) {
|
|
// throw fields in doc so that we can inspect them in test
|
|
throw new Meteor.Error(
|
|
999, "Test: Fields in doc: " + _.keys(doc).join(','));
|
|
},
|
|
fetch: ['field1']
|
|
});
|
|
restrictedCollectionForFetchTest.allow({
|
|
fetch: ['field2']
|
|
});
|
|
restrictedCollectionForFetchTest.deny({
|
|
fetch: ['field3']
|
|
});
|
|
|
|
// verify that not passing fetch to one of the calls to allow
|
|
// causes all fields to be fetched
|
|
restrictedCollectionForFetchAllTest.allow({
|
|
insert: function() { return true; },
|
|
update: function(userId, doc) {
|
|
// throw fields in doc so that we can inspect them in test
|
|
throw new Meteor.Error(
|
|
999, "Test: Fields in doc: " + _.keys(doc).join(','));
|
|
},
|
|
remove: function(userId, doc) {
|
|
// throw fields in doc so that we can inspect them in test
|
|
throw new Meteor.Error(
|
|
999, "Test: Fields in doc: " + _.keys(doc).join(','));
|
|
},
|
|
fetch: ['field1']
|
|
});
|
|
restrictedCollectionForFetchAllTest.allow({
|
|
update: function() { return true; }
|
|
});
|
|
}
|
|
|
|
return cursors;
|
|
});
|
|
}
|
|
|
|
if (Meteor.isClient) {
|
|
_.each(['STRING', 'MONGO'], function (idGeneration) {
|
|
// Set up a bunch of test collections... on the client! They match the ones
|
|
// created by setUpAllowTestsCollections.
|
|
|
|
var nonce = Random.id();
|
|
// Tell the server to make, configure, and publish a set of collections unique
|
|
// to our test run. Since the method does not unblock, this will complete
|
|
// running on the server before anything else happens.
|
|
Meteor.subscribe('allowTests', nonce, idGeneration);
|
|
|
|
// helper for defining a collection, subscribing to it, and defining
|
|
// a method to clear it
|
|
var defineCollection = function(name, transform) {
|
|
var fullName = name + idGeneration + nonce;
|
|
var collection = new Mongo.Collection(
|
|
fullName, {idGeneration: idGeneration, transform: transform});
|
|
|
|
collection.callClearMethod = function (callback) {
|
|
Meteor.call("clear-collection-" + fullName, callback);
|
|
};
|
|
collection.unnoncedName = name + idGeneration;
|
|
return collection;
|
|
};
|
|
|
|
// totally insecure collection
|
|
var insecureCollection = defineCollection("collection-insecure");
|
|
|
|
// totally locked down collection
|
|
var lockedDownCollection = defineCollection("collection-locked-down");
|
|
|
|
// restricted collection with same allowed modifications, both with and
|
|
// without the `insecure` package
|
|
var restrictedCollectionDefaultSecure = defineCollection(
|
|
"collection-restrictedDefaultSecure");
|
|
var restrictedCollectionDefaultInsecure = defineCollection(
|
|
"collection-restrictedDefaultInsecure");
|
|
var restrictedCollectionForUpdateOptionsTest = defineCollection(
|
|
"collection-restrictedForUpdateOptionsTest");
|
|
var restrictedCollectionForPartialAllowTest = defineCollection(
|
|
"collection-restrictedForPartialAllowTest");
|
|
var restrictedCollectionForPartialDenyTest = defineCollection(
|
|
"collection-restrictedForPartialDenyTest");
|
|
var restrictedCollectionForFetchTest = defineCollection(
|
|
"collection-restrictedForFetchTest");
|
|
var restrictedCollectionForFetchAllTest = defineCollection(
|
|
"collection-restrictedForFetchAllTest");
|
|
var restrictedCollectionWithTransform = defineCollection(
|
|
"withTransform", function (doc) {
|
|
return doc.a;
|
|
});
|
|
var restrictedCollectionForInvalidTransformTest = defineCollection(
|
|
"collection-restrictedForInvalidTransform");
|
|
var restrictedCollectionForClientIdTest = defineCollection(
|
|
"collection-restrictedForClientIdTest");
|
|
|
|
// test that if allow is called once then the collection is
|
|
// restricted, and that other mutations aren't allowed
|
|
testAsyncMulti("collection - partial allow, " + idGeneration, [
|
|
function (test, expect) {
|
|
restrictedCollectionForPartialAllowTest.update(
|
|
'foo', {$set: {updated: true}}, expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
}));
|
|
}
|
|
]);
|
|
|
|
// test that if deny is called once then the collection is
|
|
// restricted, and that other mutations aren't allowed
|
|
testAsyncMulti("collection - partial deny, " + idGeneration, [
|
|
function (test, expect) {
|
|
restrictedCollectionForPartialDenyTest.update(
|
|
'foo', {$set: {updated: true}}, expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
}));
|
|
}
|
|
]);
|
|
|
|
|
|
// test that we only fetch the fields specified
|
|
testAsyncMulti("collection - fetch, " + idGeneration, [
|
|
function (test, expect) {
|
|
var fetchId = restrictedCollectionForFetchTest.insert(
|
|
{field1: 1, field2: 1, field3: 1, field4: 1});
|
|
var fetchAllId = restrictedCollectionForFetchAllTest.insert(
|
|
{field1: 1, field2: 1, field3: 1, field4: 1});
|
|
restrictedCollectionForFetchTest.update(
|
|
fetchId, {$set: {updated: true}}, expect(function (err, res) {
|
|
test.equal(err.reason,
|
|
"Test: Fields in doc: _id,field1,field2,field3");
|
|
}));
|
|
restrictedCollectionForFetchTest.remove(
|
|
fetchId, expect(function (err, res) {
|
|
test.equal(err.reason,
|
|
"Test: Fields in doc: _id,field1,field2,field3");
|
|
}));
|
|
|
|
restrictedCollectionForFetchAllTest.update(
|
|
fetchAllId, {$set: {updated: true}}, expect(function (err, res) {
|
|
test.equal(err.reason,
|
|
"Test: Fields in doc: _id,field1,field2,field3,field4");
|
|
}));
|
|
restrictedCollectionForFetchAllTest.remove(
|
|
fetchAllId, expect(function (err, res) {
|
|
test.equal(err.reason,
|
|
"Test: Fields in doc: _id,field1,field2,field3,field4");
|
|
}));
|
|
}
|
|
]);
|
|
|
|
(function(){
|
|
testAsyncMulti("collection - restricted factories " + idGeneration, [
|
|
function (test, expect) {
|
|
restrictedCollectionWithTransform.callClearMethod(expect(function () {
|
|
test.equal(restrictedCollectionWithTransform.find().count(), 0);
|
|
}));
|
|
},
|
|
function (test, expect) {
|
|
var self = this;
|
|
restrictedCollectionWithTransform.insert({
|
|
a: {foo: "foo", bar: "bar", baz: "baz"}
|
|
}, expect(function (e, res) {
|
|
test.isFalse(e);
|
|
test.isTrue(res);
|
|
self.item1 = res;
|
|
}));
|
|
restrictedCollectionWithTransform.insert({
|
|
a: {foo: "foo", bar: "quux", baz: "quux"},
|
|
b: "potato"
|
|
}, expect(function (e, res) {
|
|
test.isFalse(e);
|
|
test.isTrue(res);
|
|
self.item2 = res;
|
|
}));
|
|
restrictedCollectionWithTransform.insert({
|
|
a: {foo: "adsfadf", bar: "quux", baz: "quux"},
|
|
b: "potato"
|
|
}, expect(function (e, res) {
|
|
test.isTrue(e);
|
|
}));
|
|
restrictedCollectionWithTransform.insert({
|
|
a: {foo: "bar"},
|
|
topLevelField: true
|
|
}, expect(function (e, res) {
|
|
test.isFalse(e);
|
|
test.isTrue(res);
|
|
self.item3 = res;
|
|
}));
|
|
},
|
|
function (test, expect) {
|
|
var self = this;
|
|
// This should work, because there is an update allow for things with
|
|
// topLevelField.
|
|
restrictedCollectionWithTransform.update(
|
|
self.item3, { $set: { xxx: true } }, expect(function (e, res) {
|
|
test.isFalse(e);
|
|
test.equal(1, res);
|
|
}));
|
|
},
|
|
function (test, expect) {
|
|
var self = this;
|
|
test.equal(
|
|
restrictedCollectionWithTransform.findOne(self.item1),
|
|
{_id: self.item1, foo: "foo", bar: "bar", baz: "baz"});
|
|
restrictedCollectionWithTransform.remove(
|
|
self.item1, expect(function (e, res) {
|
|
test.isFalse(e);
|
|
}));
|
|
restrictedCollectionWithTransform.remove(
|
|
self.item2, expect(function (e, res) {
|
|
test.isTrue(e);
|
|
}));
|
|
}
|
|
]);
|
|
})();
|
|
|
|
testAsyncMulti("collection - insecure, " + idGeneration, [
|
|
function (test, expect) {
|
|
insecureCollection.callClearMethod(expect(function () {
|
|
test.equal(insecureCollection.find().count(), 0);
|
|
}));
|
|
},
|
|
function (test, expect) {
|
|
var id = insecureCollection.insert({foo: 'bar'}, expect(function(err, res) {
|
|
test.equal(res, id);
|
|
test.equal(insecureCollection.find(id).count(), 1);
|
|
test.equal(insecureCollection.findOne(id).foo, 'bar');
|
|
}));
|
|
test.equal(insecureCollection.find(id).count(), 1);
|
|
test.equal(insecureCollection.findOne(id).foo, 'bar');
|
|
}
|
|
]);
|
|
|
|
testAsyncMulti("collection - locked down, " + idGeneration, [
|
|
function (test, expect) {
|
|
lockedDownCollection.callClearMethod(expect(function() {
|
|
test.equal(lockedDownCollection.find().count(), 0);
|
|
}));
|
|
},
|
|
function (test, expect) {
|
|
lockedDownCollection.insert({foo: 'bar'}, expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
test.equal(lockedDownCollection.find().count(), 0);
|
|
}));
|
|
}
|
|
]);
|
|
|
|
(function () {
|
|
var collection = restrictedCollectionForUpdateOptionsTest;
|
|
var id1, id2;
|
|
testAsyncMulti("collection - update options, " + idGeneration, [
|
|
// init
|
|
function (test, expect) {
|
|
collection.callClearMethod(expect(function () {
|
|
test.equal(collection.find().count(), 0);
|
|
}));
|
|
},
|
|
// put a few objects
|
|
function (test, expect) {
|
|
var doc = {canInsert: true, canUpdate: true};
|
|
id1 = collection.insert(doc);
|
|
id2 = collection.insert(doc);
|
|
collection.insert(doc);
|
|
collection.insert(doc, expect(function (err, res) {
|
|
test.isFalse(err);
|
|
test.equal(collection.find().count(), 4);
|
|
}));
|
|
},
|
|
// update by id
|
|
function (test, expect) {
|
|
collection.update(
|
|
id1,
|
|
{$set: {updated: true}},
|
|
expect(function (err, res) {
|
|
test.isFalse(err);
|
|
test.equal(res, 1);
|
|
test.equal(collection.find({updated: true}).count(), 1);
|
|
}));
|
|
},
|
|
// update by id in an object
|
|
function (test, expect) {
|
|
collection.update(
|
|
{_id: id2},
|
|
{$set: {updated: true}},
|
|
expect(function (err, res) {
|
|
test.isFalse(err);
|
|
test.equal(res, 1);
|
|
test.equal(collection.find({updated: true}).count(), 2);
|
|
}));
|
|
},
|
|
// update with replacement operator not allowed, and has nice error.
|
|
function (test, expect) {
|
|
collection.update(
|
|
{_id: id2},
|
|
{_id: id2, updated: true},
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
test.matches(err.reason, /In a restricted/);
|
|
// unchanged
|
|
test.equal(collection.find({updated: true}).count(), 2);
|
|
}));
|
|
},
|
|
// upsert not allowed, and has nice error.
|
|
function (test, expect) {
|
|
collection.update(
|
|
{_id: id2},
|
|
{$set: { upserted: true }},
|
|
{ upsert: true },
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
test.matches(err.reason, /in a restricted/);
|
|
test.equal(collection.find({ upserted: true }).count(), 0);
|
|
}));
|
|
},
|
|
// update with rename operator not allowed, and has nice error.
|
|
function (test, expect) {
|
|
collection.update(
|
|
{_id: id2},
|
|
{$rename: {updated: 'asdf'}},
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
test.matches(err.reason, /not allowed/);
|
|
// unchanged
|
|
test.equal(collection.find({updated: true}).count(), 2);
|
|
}));
|
|
},
|
|
// update method with a non-ID selector is not allowed
|
|
function (test, expect) {
|
|
// We shouldn't even send the method...
|
|
test.throws(function () {
|
|
collection.update(
|
|
{updated: {$exists: false}},
|
|
{$set: {updated: true}});
|
|
});
|
|
// ... but if we did, the server would reject it too.
|
|
Meteor.call(
|
|
'/' + collection._name + '/update',
|
|
{updated: {$exists: false}},
|
|
{$set: {updated: true}},
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
// unchanged
|
|
test.equal(collection.find({updated: true}).count(), 2);
|
|
}));
|
|
},
|
|
// make sure it doesn't think that {_id: 'foo', something: else} is ok.
|
|
function (test, expect) {
|
|
test.throws(function () {
|
|
collection.update(
|
|
{_id: id1, updated: {$exists: false}},
|
|
{$set: {updated: true}});
|
|
});
|
|
},
|
|
// remove method with a non-ID selector is not allowed
|
|
function (test, expect) {
|
|
// We shouldn't even send the method...
|
|
test.throws(function () {
|
|
collection.remove({updated: true});
|
|
});
|
|
// ... but if we did, the server would reject it too.
|
|
Meteor.call(
|
|
'/' + collection._name + '/remove',
|
|
{updated: true},
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
// unchanged
|
|
test.equal(collection.find({updated: true}).count(), 2);
|
|
}));
|
|
}
|
|
]);
|
|
}) ();
|
|
|
|
_.each(
|
|
[restrictedCollectionDefaultInsecure, restrictedCollectionDefaultSecure],
|
|
function(collection) {
|
|
var canUpdateId, canRemoveId;
|
|
|
|
testAsyncMulti("collection - " + collection.unnoncedName, [
|
|
// init
|
|
function (test, expect) {
|
|
collection.callClearMethod(expect(function () {
|
|
test.equal(collection.find().count(), 0);
|
|
}));
|
|
},
|
|
|
|
// insert with no allows passing. request is denied.
|
|
function (test, expect) {
|
|
collection.insert(
|
|
{},
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
test.equal(collection.find().count(), 0);
|
|
}));
|
|
},
|
|
// insert with one allow and one deny. denied.
|
|
function (test, expect) {
|
|
collection.insert(
|
|
{canInsert: true, cantInsert: true},
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
test.equal(collection.find().count(), 0);
|
|
}));
|
|
},
|
|
// insert with one allow and other deny. denied.
|
|
function (test, expect) {
|
|
collection.insert(
|
|
{canInsert: true, _id: Random.id()},
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
test.equal(collection.find().count(), 0);
|
|
}));
|
|
},
|
|
// insert one allow passes. allowed.
|
|
function (test, expect) {
|
|
collection.insert(
|
|
{canInsert: true},
|
|
expect(function (err, res) {
|
|
test.isFalse(err);
|
|
test.equal(collection.find().count(), 1);
|
|
}));
|
|
},
|
|
// insert other allow passes. allowed.
|
|
// includes canUpdate for later.
|
|
function (test, expect) {
|
|
canUpdateId = collection.insert(
|
|
{canInsert2: true, canUpdate: true},
|
|
expect(function (err, res) {
|
|
test.isFalse(err);
|
|
test.equal(collection.find().count(), 2);
|
|
}));
|
|
},
|
|
// yet a third insert executes. this one has canRemove and
|
|
// cantRemove set for later.
|
|
function (test, expect) {
|
|
canRemoveId = collection.insert(
|
|
{canInsert: true, canRemove: true, cantRemove: true},
|
|
expect(function (err, res) {
|
|
test.isFalse(err);
|
|
test.equal(collection.find().count(), 3);
|
|
}));
|
|
},
|
|
|
|
// can't update with a non-operator mutation
|
|
function (test, expect) {
|
|
collection.update(
|
|
canUpdateId, {newObject: 1},
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
test.equal(collection.find().count(), 3);
|
|
}));
|
|
},
|
|
|
|
// updating dotted fields works as if we are changing their
|
|
// top part
|
|
function (test, expect) {
|
|
collection.update(
|
|
canUpdateId, {$set: {"dotted.field": 1}},
|
|
expect(function (err, res) {
|
|
test.isFalse(err);
|
|
test.equal(res, 1);
|
|
test.equal(collection.findOne(canUpdateId).dotted.field, 1);
|
|
}));
|
|
},
|
|
function (test, expect) {
|
|
collection.update(
|
|
canUpdateId, {$set: {"verySecret.field": 1}},
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
test.equal(collection.find({verySecret: {$exists: true}}).count(), 0);
|
|
}));
|
|
},
|
|
|
|
// update doesn't do anything if no docs match
|
|
function (test, expect) {
|
|
collection.update(
|
|
"doesn't exist",
|
|
{$set: {updated: true}},
|
|
expect(function (err, res) {
|
|
test.isFalse(err);
|
|
test.equal(res, 0);
|
|
// nothing has changed
|
|
test.equal(collection.find().count(), 3);
|
|
test.equal(collection.find({updated: true}).count(), 0);
|
|
}));
|
|
},
|
|
// update fails when access is denied trying to set `verySecret`
|
|
function (test, expect) {
|
|
collection.update(
|
|
canUpdateId, {$set: {verySecret: true}},
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
// nothing has changed
|
|
test.equal(collection.find().count(), 3);
|
|
test.equal(collection.find({updated: true}).count(), 0);
|
|
}));
|
|
},
|
|
// update fails when trying to set two fields, one of which is
|
|
// `verySecret`
|
|
function (test, expect) {
|
|
collection.update(
|
|
canUpdateId, {$set: {updated: true, verySecret: true}},
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
// nothing has changed
|
|
test.equal(collection.find().count(), 3);
|
|
test.equal(collection.find({updated: true}).count(), 0);
|
|
}));
|
|
},
|
|
// update fails when trying to modify docs that don't
|
|
// have `canUpdate` set
|
|
function (test, expect) {
|
|
collection.update(
|
|
canRemoveId,
|
|
{$set: {updated: true}},
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
// nothing has changed
|
|
test.equal(collection.find().count(), 3);
|
|
test.equal(collection.find({updated: true}).count(), 0);
|
|
}));
|
|
},
|
|
// update executes when it should
|
|
function (test, expect) {
|
|
collection.update(
|
|
canUpdateId,
|
|
{$set: {updated: true}},
|
|
expect(function (err, res) {
|
|
test.isFalse(err);
|
|
test.equal(res, 1);
|
|
test.equal(collection.find({updated: true}).count(), 1);
|
|
}));
|
|
},
|
|
|
|
// remove fails when trying to modify a doc with no `canRemove` set
|
|
function (test, expect) {
|
|
collection.remove(canUpdateId,
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
// nothing has changed
|
|
test.equal(collection.find().count(), 3);
|
|
}));
|
|
},
|
|
// remove fails when trying to modify an doc with `cantRemove`
|
|
// set
|
|
function (test, expect) {
|
|
collection.remove(canRemoveId,
|
|
expect(function (err, res) {
|
|
test.equal(err.error, 403);
|
|
// nothing has changed
|
|
test.equal(collection.find().count(), 3);
|
|
}));
|
|
},
|
|
|
|
// update the doc to remove cantRemove.
|
|
function (test, expect) {
|
|
collection.update(
|
|
canRemoveId,
|
|
{$set: {cantRemove: false, canUpdate2: true}},
|
|
expect(function (err, res) {
|
|
test.isFalse(err);
|
|
test.equal(res, 1);
|
|
test.equal(collection.find({cantRemove: true}).count(), 0);
|
|
}));
|
|
},
|
|
|
|
// now remove can remove it.
|
|
function (test, expect) {
|
|
collection.remove(canRemoveId,
|
|
expect(function (err, res) {
|
|
test.isFalse(err);
|
|
test.equal(res, 1);
|
|
// successfully removed
|
|
test.equal(collection.find().count(), 2);
|
|
}));
|
|
},
|
|
|
|
// try to remove a doc that doesn't exist. see we remove no docs.
|
|
function (test, expect) {
|
|
collection.remove('some-random-id-that-never-matches',
|
|
expect(function (err, res) {
|
|
test.isFalse(err);
|
|
test.equal(res, 0);
|
|
// nothing removed
|
|
test.equal(collection.find().count(), 2);
|
|
}));
|
|
},
|
|
|
|
// methods can still bypass restrictions
|
|
function (test, expect) {
|
|
collection.callClearMethod(
|
|
expect(function (err, res) {
|
|
test.isFalse(err);
|
|
// successfully removed
|
|
test.equal(collection.find().count(), 0);
|
|
}));
|
|
}
|
|
]);
|
|
});
|
|
testAsyncMulti(
|
|
"collection - allow/deny transform must return object, " + idGeneration,
|
|
[function (test, expect) {
|
|
restrictedCollectionForInvalidTransformTest.insert({}, expect(function (err, res) {
|
|
test.isTrue(err);
|
|
}));
|
|
}]);
|
|
testAsyncMulti(
|
|
"collection - restricted collection allows client-side id, " + idGeneration,
|
|
[function (test, expect) {
|
|
var self = this;
|
|
self.id = Random.id();
|
|
restrictedCollectionForClientIdTest.insert({_id: self.id}, expect(function (err, res) {
|
|
test.isFalse(err);
|
|
test.equal(res, self.id);
|
|
test.equal(restrictedCollectionForClientIdTest.findOne(self.id),
|
|
{_id: self.id});
|
|
}));
|
|
}]);
|
|
}); // end idGeneration loop
|
|
} // end if isClient
|
|
|
|
|
|
|
|
// A few simple server-only tests which don't need to coordinate collections
|
|
// with the client..
|
|
if (Meteor.isServer) {
|
|
Tinytest.add("collection - allow and deny validate options", function (test) {
|
|
var collection = new Mongo.Collection(null);
|
|
|
|
test.throws(function () {
|
|
collection.allow({invalidOption: true});
|
|
});
|
|
test.throws(function () {
|
|
collection.deny({invalidOption: true});
|
|
});
|
|
|
|
_.each(['insert', 'update', 'remove', 'fetch'], function (key) {
|
|
var options = {};
|
|
options[key] = true;
|
|
test.throws(function () {
|
|
collection.allow(options);
|
|
});
|
|
test.throws(function () {
|
|
collection.deny(options);
|
|
});
|
|
});
|
|
|
|
_.each(['insert', 'update', 'remove'], function (key) {
|
|
var options = {};
|
|
options[key] = ['an array']; // this should be a function, not an array
|
|
test.throws(function () {
|
|
collection.allow(options);
|
|
});
|
|
test.throws(function () {
|
|
collection.deny(options);
|
|
});
|
|
});
|
|
|
|
test.throws(function () {
|
|
collection.allow({fetch: function () {}}); // this should be an array
|
|
});
|
|
});
|
|
|
|
Tinytest.add("collection - calling allow restricts", function (test) {
|
|
var collection = new Mongo.Collection(null);
|
|
test.equal(collection._restricted, false);
|
|
collection.allow({
|
|
insert: function() {}
|
|
});
|
|
test.equal(collection._restricted, true);
|
|
});
|
|
|
|
Tinytest.add("collection - global insecure", function (test) {
|
|
// note: This test alters the global insecure status, by sneakily hacking
|
|
// the global Package object!
|
|
var insecurePackage = Package.insecure;
|
|
|
|
Package.insecure = {};
|
|
var collection = new Mongo.Collection(null);
|
|
test.equal(collection._isInsecure(), true);
|
|
|
|
Package.insecure = undefined;
|
|
test.equal(collection._isInsecure(), false);
|
|
|
|
delete Package.insecure;
|
|
test.equal(collection._isInsecure(), false);
|
|
|
|
collection._insecure = true;
|
|
test.equal(collection._isInsecure(), true);
|
|
|
|
if (insecurePackage)
|
|
Package.insecure = insecurePackage;
|
|
else
|
|
delete Package.insecure;
|
|
});
|
|
}
|