Replace Underscore methods with native ECMAScript.

This removes `underscore` entirely from `allow-deny`.
This commit is contained in:
Jesse Rosenberger
2017-09-06 00:23:41 +03:00
parent 8983eeae87
commit 29a3fbda6a
2 changed files with 29 additions and 23 deletions

View File

@@ -2,6 +2,8 @@
/// Remote methods and access control.
///
const hasOwn = Object.prototype.hasOwnProperty;
// Restrict default mutators on collection. allow() and deny() take the
// same options:
//
@@ -108,7 +110,7 @@ CollectionPrototype._defineMutationMethods = function(options) {
if (self._connection && (self._connection === Meteor.server || Meteor.isClient)) {
const m = {};
_.each(['insert', 'update', 'remove'], function (method) {
['insert', 'update', 'remove'].forEach((method) => {
const methodName = self._prefix + method;
if (options.useExisting) {
@@ -122,7 +124,7 @@ CollectionPrototype._defineMutationMethods = function(options) {
m[methodName] = function (/* ... */) {
// All the methods do their own validation, instead of using check().
check(arguments, [Match.Any]);
const args = _.toArray(arguments);
const args = Array.from(arguments);
try {
// For an insert, if the client didn't specify an _id, generate one
// now; because this uses DDP.randomStream, it will be consistent with
@@ -136,7 +138,7 @@ CollectionPrototype._defineMutationMethods = function(options) {
// between arbitrary client-specified _id fields and merely
// client-controlled-via-randomSeed fields.
let generatedId = null;
if (method === "insert" && !_.has(args[0], '_id')) {
if (method === "insert" && !hasOwn.call(args[0], '_id')) {
generatedId = self._makeNewID();
}
@@ -208,7 +210,9 @@ CollectionPrototype._updateFetch = function (fields) {
if (!self._validators.fetchAllFields) {
if (fields) {
self._validators.fetch = _.union(self._validators.fetch, fields);
self._validators.fetch =
// union
Array.from(new Set([...self._validators.fetch, ...fields]));
} else {
self._validators.fetchAllFields = true;
// clear fetch just to make sure we don't accidentally read it
@@ -230,13 +234,13 @@ CollectionPrototype._validatedInsert = function (userId, doc,
// call user validators.
// Any deny returns true means denied.
if (_.any(self._validators.insert.deny, function(validator) {
if (self._validators.insert.deny.some((validator) => {
return validator(userId, docToValidate(validator, doc, generatedId));
})) {
throw new Meteor.Error(403, "Access denied");
}
// Any allow returns true means proceed. Throw error if they all fail.
if (_.all(self._validators.insert.allow, function(validator) {
if (self._validators.insert.allow.every((validator) => {
return !validator(userId, docToValidate(validator, doc, generatedId));
})) {
throw new Meteor.Error(403, "Access denied");
@@ -260,7 +264,7 @@ CollectionPrototype._validatedUpdate = function(
check(mutator, Object);
options = _.clone(options) || {};
options = Object.assign(Object.create(null), options);
if (!LocalCollection._selectorIsIdPerhapsAsObject(selector))
throw new Error("validated update should be of a single ID");
@@ -275,26 +279,29 @@ CollectionPrototype._validatedUpdate = function(
" update documents, not replace them. Use a Mongo update operator, such " +
"as '$set'.";
const mutatorKeys = Object.keys(mutator);
// compute modified fields
const fields = [];
if (_.isEmpty(mutator)) {
if (mutatorKeys.length === 0) {
throw new Meteor.Error(403, noReplaceError);
}
_.each(mutator, function (params, op) {
mutatorKeys.forEach((op) => {
const params = mutator[op];
if (op.charAt(0) !== '$') {
throw new Meteor.Error(403, noReplaceError);
} else if (!_.has(ALLOWED_UPDATE_OPERATIONS, op)) {
} else if (!hasOwn.call(ALLOWED_UPDATE_OPERATIONS, op)) {
throw new Meteor.Error(
403, "Access denied. Operator " + op + " not allowed in a restricted collection.");
} else {
_.each(_.keys(params), function (field) {
Object.keys(params).forEach((field) => {
// treat dotted fields as if they are replacing their
// top-level part
if (field.indexOf('.') !== -1)
field = field.substring(0, field.indexOf('.'));
// record the field we are trying to change
if (!_.contains(fields, field))
if (!fields.includes(field))
fields.push(field);
});
}
@@ -303,7 +310,7 @@ CollectionPrototype._validatedUpdate = function(
const findOptions = {transform: null};
if (!self._validators.fetchAllFields) {
findOptions.fields = {};
_.each(self._validators.fetch, function(fieldName) {
self._validators.fetch.forEach((fieldName) => {
findOptions.fields[fieldName] = 1;
});
}
@@ -314,7 +321,7 @@ CollectionPrototype._validatedUpdate = function(
// call user validators.
// Any deny returns true means denied.
if (_.any(self._validators.update.deny, function(validator) {
if (self._validators.update.deny.some((validator) => {
const factoriedDoc = transformDoc(validator, doc);
return validator(userId,
factoriedDoc,
@@ -324,7 +331,7 @@ CollectionPrototype._validatedUpdate = function(
throw new Meteor.Error(403, "Access denied");
}
// Any allow returns true means proceed. Throw error if they all fail.
if (_.all(self._validators.update.allow, function(validator) {
if (self._validators.update.allow.every((validator) => {
const factoriedDoc = transformDoc(validator, doc);
return !validator(userId,
factoriedDoc,
@@ -364,7 +371,7 @@ CollectionPrototype._validatedRemove = function(userId, selector) {
const findOptions = {transform: null};
if (!self._validators.fetchAllFields) {
findOptions.fields = {};
_.each(self._validators.fetch, function(fieldName) {
self._validators.fetch.forEach((fieldName) => {
findOptions.fields[fieldName] = 1;
});
}
@@ -375,13 +382,13 @@ CollectionPrototype._validatedRemove = function(userId, selector) {
// call user validators.
// Any deny returns true means denied.
if (_.any(self._validators.remove.deny, function(validator) {
if (self._validators.remove.deny.some((validator) => {
return validator(userId, transformDoc(validator, doc));
})) {
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) {
if (self._validators.remove.allow.every((validator) => {
return !validator(userId, transformDoc(validator, doc));
})) {
throw new Meteor.Error(403, "Access denied");
@@ -451,15 +458,15 @@ function docToValidate(validator, doc, generatedId) {
function addValidator(collection, allowOrDeny, options) {
// validate keys
const VALID_KEYS = ['insert', 'update', 'remove', 'fetch', 'transform'];
_.each(_.keys(options), function (key) {
if (!_.contains(VALID_KEYS, key))
Object.keys(options).forEach((key) => {
if (!VALID_KEYS.includes(key))
throw new Error(allowOrDeny + ": Invalid key: " + key);
});
collection._restricted = true;
_.each(['insert', 'update', 'remove'], function (name) {
if (options.hasOwnProperty(name)) {
['insert', 'update', 'remove'].forEach((name) => {
if (hasOwn.call(options, name)) {
if (!(options[name] instanceof Function)) {
throw new Error(allowOrDeny + ": Value for `" + name + "` must be a function");
}

View File

@@ -13,7 +13,6 @@ Package.describe({
Package.onUse(function(api) {
api.use([
'ecmascript',
'underscore',
'minimongo', // Just for LocalCollection.wrapTransform :[
'check',
'ejson',