////////////////////////////////////////////////////////////////////////// // // // This is a generated file. You can view the original // // source in your browser if your browser supports source maps. // // Source maps are supported by all recent versions of Chrome, Safari, // // and Firefox, and by Internet Explorer 11. // // // ////////////////////////////////////////////////////////////////////////// (function () { /* Imports */ var Meteor = Package.meteor.Meteor; var global = Package.meteor.global; var meteorEnv = Package.meteor.meteorEnv; var LocalCollection = Package.minimongo.LocalCollection; var Minimongo = Package.minimongo.Minimongo; var check = Package.check.check; var Match = Package.check.Match; var EJSON = Package.ejson.EJSON; var DDP = Package['ddp-client'].DDP; var meteorInstall = Package.modules.meteorInstall; var meteorBabelHelpers = Package['babel-runtime'].meteorBabelHelpers; var Promise = Package.promise.Promise; var Symbol = Package['ecmascript-runtime-client'].Symbol; var Map = Package['ecmascript-runtime-client'].Map; var Set = Package['ecmascript-runtime-client'].Set; /* Package-scope variables */ var AllowDeny; var require = meteorInstall({"node_modules":{"meteor":{"allow-deny":{"allow-deny.js":function(){ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // // packages/allow-deny/allow-deny.js // // // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // /// /// Remote methods and access control. /// var hasOwn = Object.prototype.hasOwnProperty; // Restrict default mutators on collection. allow() and deny() take the // same options: // // options.insert {Function(userId, doc)} // return true to allow/deny adding this document // // options.update {Function(userId, docs, fields, modifier)} // return true to allow/deny updating these documents. // `fields` is passed as an array of fields that are to be modified // // options.remove {Function(userId, docs)} // return true to allow/deny removing these documents // // options.fetch {Array} // Fields to fetch for these validators. If any call to allow or deny // does not have this option then all fields are loaded. // // allow and deny can be called multiple times. The validators are // evaluated as follows: // - If neither deny() nor allow() has been called on the collection, // then the request is allowed if and only if the "insecure" smart // package is in use. // - Otherwise, if any deny() function returns true, the request is denied. // - Otherwise, if any allow() function returns true, the request is allowed. // - Otherwise, the request is denied. // // Meteor may call your deny() and allow() functions in any order, and may not // call all of them if it is able to make a decision without calling them all // (so don't include side effects). AllowDeny = { CollectionPrototype: {} }; // In the `mongo` package, we will extend Mongo.Collection.prototype with these // methods var CollectionPrototype = AllowDeny.CollectionPrototype; /** * @summary Allow users to write directly to this collection from client code, subject to limitations you define. * @locus Server * @method allow * @memberOf Mongo.Collection * @instance * @param {Object} options * @param {Function} options.insert,update,remove Functions that look at a proposed modification to the database and return true if it should be allowed. * @param {String[]} options.fetch Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your `update` and `remove` functions. * @param {Function} options.transform Overrides `transform` on the [`Collection`](#collections). Pass `null` to disable transformation. */ CollectionPrototype.allow = function (options) { addValidator(this, 'allow', options); }; /** * @summary Override `allow` rules. * @locus Server * @method deny * @memberOf Mongo.Collection * @instance * @param {Object} options * @param {Function} options.insert,update,remove Functions that look at a proposed modification to the database and return true if it should be denied, even if an [allow](#allow) rule says otherwise. * @param {String[]} options.fetch Optional performance enhancement. Limits the fields that will be fetched from the database for inspection by your `update` and `remove` functions. * @param {Function} options.transform Overrides `transform` on the [`Collection`](#collections). Pass `null` to disable transformation. */ CollectionPrototype.deny = function (options) { addValidator(this, 'deny', options); }; CollectionPrototype._defineMutationMethods = function (options) { var self = this; options = options || {}; // set to true once we call any allow or deny methods. If true, use // allow/deny semantics. If false, use insecure mode semantics. self._restricted = false; // Insecure mode (default to allowing writes). Defaults to 'undefined' which // means insecure iff the insecure package is loaded. This property can be // overriden by tests or packages wishing to change insecure mode behavior of // their collections. self._insecure = undefined; self._validators = { insert: { allow: [], deny: [] }, update: { allow: [], deny: [] }, remove: { allow: [], deny: [] }, upsert: { allow: [], deny: [] }, // dummy arrays; can't set these! fetch: [], fetchAllFields: false }; if (!self._name) return; // anonymous collection // XXX Think about method namespacing. Maybe methods should be // "Meteor:Mongo:insert/NAME"? self._prefix = '/' + self._name + '/'; // Mutation Methods // Minimongo on the server gets no stubs; instead, by default // it wait()s until its result is ready, yielding. // This matches the behavior of macromongo on the server better. // XXX see #MeteorServerNull if (self._connection && (self._connection === Meteor.server || Meteor.isClient)) { var m = {}; ['insert', 'update', 'remove'].forEach(function (method) { var methodName = self._prefix + method; if (options.useExisting) { var handlerPropName = Meteor.isClient ? '_methodHandlers' : 'method_handlers'; // Do not try to create additional methods if this has already been called. // (Otherwise the .methods() call below will throw an error.) if (self._connection[handlerPropName] && typeof self._connection[handlerPropName][methodName] === 'function') return; } m[methodName] = function () /* ... */ { // All the methods do their own validation, instead of using check(). check(arguments, [Match.Any]); var 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 // what the client generated. We generate it now rather than later so // that if (eg) an allow/deny rule does an insert to the same // collection (not that it really should), the generated _id will // still be the first use of the stream and will be consistent. // // However, we don't actually stick the _id onto the document yet, // because we want allow/deny rules to be able to differentiate // between arbitrary client-specified _id fields and merely // client-controlled-via-randomSeed fields. var generatedId = null; if (method === "insert" && !hasOwn.call(args[0], '_id')) { generatedId = self._makeNewID(); } if (this.isSimulation) { // In a client simulation, you can do any mutation (even with a // complex selector). if (generatedId !== null) args[0]._id = generatedId; return self._collection[method].apply(self._collection, args); } // This is the server receiving a method call from the client. // We don't allow arbitrary selectors in mutations from the client: only // single-ID selectors. if (method !== 'insert') throwIfSelectorIsNotId(args[0], method); if (self._restricted) { // short circuit if there is no way it will pass. if (self._validators[method].allow.length === 0) { throw new Meteor.Error(403, "Access denied. No allow validators set on restricted " + "collection for method '" + method + "'."); } var validatedMethodName = '_validated' + method.charAt(0).toUpperCase() + method.slice(1); args.unshift(this.userId); method === 'insert' && args.push(generatedId); return self[validatedMethodName].apply(self, args); } else if (self._isInsecure()) { if (generatedId !== null) args[0]._id = generatedId; // In insecure mode, allow any mutation (with a simple selector). // XXX This is kind of bogus. Instead of blindly passing whatever // we get from the network to this function, we should actually // know the correct arguments for the function and pass just // them. For example, if you have an extraneous extra null // argument and this is Mongo on the server, the .wrapAsync'd // functions like update will get confused and pass the // "fut.resolver()" in the wrong slot, where _update will never // invoke it. Bam, broken DDP connection. Probably should just // take this whole method and write it three times, invoking // helpers for the common code. return self._collection[method].apply(self._collection, args); } else { // In secure mode, if we haven't called allow or deny, then nothing // is permitted. throw new Meteor.Error(403, "Access denied"); } } catch (e) { if (e.name === 'MongoError' || e.name === 'MinimongoError') { throw new Meteor.Error(409, e.toString()); } else { throw e; } } }; }); self._connection.methods(m); } }; CollectionPrototype._updateFetch = function (fields) { var self = this; if (!self._validators.fetchAllFields) { if (fields) { var union = Object.create(null); var add = function (names) { return names && names.forEach(function (name) { return union[name] = 1; }); }; add(self._validators.fetch); add(fields); self._validators.fetch = Object.keys(union); } else { self._validators.fetchAllFields = true; // clear fetch just to make sure we don't accidentally read it self._validators.fetch = null; } } }; CollectionPrototype._isInsecure = function () { var self = this; if (self._insecure === undefined) return !!Package.insecure; return self._insecure; }; CollectionPrototype._validatedInsert = function (userId, doc, generatedId) { var self = this; // call user validators. // Any deny returns true means denied. if (self._validators.insert.deny.some(function (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 (self._validators.insert.allow.every(function (validator) { return !validator(userId, docToValidate(validator, doc, generatedId)); })) { throw new Meteor.Error(403, "Access denied"); } // If we generated an ID above, insert it now: after the validation, but // before actually inserting. if (generatedId !== null) doc._id = generatedId; self._collection.insert.call(self._collection, doc); }; // 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 // document ids to change ##ValidatedChange CollectionPrototype._validatedUpdate = function (userId, selector, mutator, options) { var self = this; check(mutator, Object); options = Object.assign(Object.create(null), options); if (!LocalCollection._selectorIsIdPerhapsAsObject(selector)) throw new Error("validated update should be of a single ID"); // We don't support upserts because they don't fit nicely into allow/deny // rules. if (options.upsert) throw new Meteor.Error(403, "Access denied. Upserts not " + "allowed in a restricted collection."); var noReplaceError = "Access denied. In a restricted collection you can only" + " update documents, not replace them. Use a Mongo update operator, such " + "as '$set'."; var mutatorKeys = Object.keys(mutator); // compute modified fields var modifiedFields = {}; if (mutatorKeys.length === 0) { throw new Meteor.Error(403, noReplaceError); } mutatorKeys.forEach(function (op) { var params = mutator[op]; if (op.charAt(0) !== '$') { throw new Meteor.Error(403, noReplaceError); } else if (!hasOwn.call(ALLOWED_UPDATE_OPERATIONS, op)) { throw new Meteor.Error(403, "Access denied. Operator " + op + " not allowed in a restricted collection."); } else { Object.keys(params).forEach(function (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 modifiedFields[field] = true; }); } }); var fields = Object.keys(modifiedFields); var findOptions = { transform: null }; if (!self._validators.fetchAllFields) { findOptions.fields = {}; self._validators.fetch.forEach(function (fieldName) { findOptions.fields[fieldName] = 1; }); } var doc = self._collection.findOne(selector, findOptions); if (!doc) // none satisfied! return 0; // call user validators. // Any deny returns true means denied. if (self._validators.update.deny.some(function (validator) { var factoriedDoc = transformDoc(validator, doc); return validator(userId, factoriedDoc, fields, mutator); })) { throw new Meteor.Error(403, "Access denied"); } // Any allow returns true means proceed. Throw error if they all fail. if (self._validators.update.allow.every(function (validator) { var factoriedDoc = transformDoc(validator, doc); return !validator(userId, factoriedDoc, fields, mutator); })) { throw new Meteor.Error(403, "Access denied"); } options._forbidReplace = true; // Back when we supported arbitrary client-provided selectors, we actually // rewrote the selector to include an _id clause before passing to Mongo to // avoid races, but since selector is guaranteed to already just be an ID, we // don't have to any more. return self._collection.update.call(self._collection, selector, mutator, options); }; // Only allow these operations in validated updates. Specifically // whitelist operations, rather than blacklist, so new complex // operations that are added aren't automatically allowed. A complex // operation is one that does more than just modify its target // field. For now this contains all update operations except '$rename'. // http://docs.mongodb.org/manual/reference/operators/#update var ALLOWED_UPDATE_OPERATIONS = { $inc: 1, $set: 1, $unset: 1, $addToSet: 1, $pop: 1, $pullAll: 1, $pull: 1, $pushAll: 1, $push: 1, $bit: 1 }; // Simulate a mongo `remove` operation while validating access control // rules. See #ValidatedChange CollectionPrototype._validatedRemove = function (userId, selector) { var self = this; var findOptions = { transform: null }; if (!self._validators.fetchAllFields) { findOptions.fields = {}; self._validators.fetch.forEach(function (fieldName) { findOptions.fields[fieldName] = 1; }); } var doc = self._collection.findOne(selector, findOptions); if (!doc) return 0; // call user validators. // Any deny returns true means denied. if (self._validators.remove.deny.some(function (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 (self._validators.remove.allow.every(function (validator) { return !validator(userId, transformDoc(validator, doc)); })) { throw new Meteor.Error(403, "Access denied"); } // Back when we supported arbitrary client-provided selectors, we actually // rewrote the selector to {_id: {$in: [ids that we found]}} before passing to // Mongo to avoid races, but since selector is guaranteed to already just be // an ID, we don't have to any more. return self._collection.remove.call(self._collection, selector); }; CollectionPrototype._callMutatorMethod = function () { function _callMutatorMethod(name, args, callback) { if (Meteor.isClient && !callback && !alreadyInSimulation()) { // Client can't block, so it can't report errors by exception, // only by callback. If they forget the callback, give them a // default one that logs the error, so they aren't totally // baffled if their writes don't work because their database is // down. // Don't give a default callback in simulation, because inside stubs we // want to return the results from the local collection immediately and // not force a callback. callback = function (err) { if (err) Meteor._debug(name + " failed: " + (err.reason || err.stack)); }; } // For two out of three mutator methods, the first argument is a selector var firstArgIsSelector = name === "update" || name === "remove"; if (firstArgIsSelector && !alreadyInSimulation()) { // If we're about to actually send an RPC, we should throw an error if // this is a non-ID selector, because the mutation methods only allow // single-ID selectors. (If we don't throw here, we'll see flicker.) throwIfSelectorIsNotId(args[0], name); } var mutatorMethodName = this._prefix + name; return this._connection.apply(mutatorMethodName, args, { returnStubValue: true }, callback); } return _callMutatorMethod; }(); function transformDoc(validator, doc) { if (validator.transform) return validator.transform(doc); return doc; } function docToValidate(validator, doc, generatedId) { var ret = doc; if (validator.transform) { ret = EJSON.clone(doc); // If you set a server-side transform on your collection, then you don't get // to tell the difference between "client specified the ID" and "server // generated the ID", because transforms expect to get _id. If you want to // do that check, you can do it with a specific // `C.allow({insert: f, transform: null})` validator. if (generatedId !== null) { ret._id = generatedId; } ret = validator.transform(ret); } return ret; } function addValidator(collection, allowOrDeny, options) { // validate keys var validKeysRegEx = /^(?:insert|update|remove|fetch|transform)$/; Object.keys(options).forEach(function (key) { if (!validKeysRegEx.test(key)) throw new Error(allowOrDeny + ": Invalid key: " + key); }); collection._restricted = true; ['insert', 'update', 'remove'].forEach(function (name) { if (hasOwn.call(options, name)) { if (!(options[name] instanceof Function)) { throw new Error(allowOrDeny + ": Value for `" + name + "` must be a function"); } // If the transform is specified at all (including as 'null') in this // call, then take that; otherwise, take the transform from the // collection. if (options.transform === undefined) { options[name].transform = collection._transform; // already wrapped } else { options[name].transform = LocalCollection.wrapTransform(options.transform); } collection._validators[name][allowOrDeny].push(options[name]); } }); // Only update the fetch fields if we're passed things that affect // fetching. This way allow({}) and allow({insert: f}) don't result in // setting fetchAllFields if (options.update || options.remove || options.fetch) { if (options.fetch && !(options.fetch instanceof Array)) { throw new Error(allowOrDeny + ": Value for `fetch` must be an array"); } collection._updateFetch(options.fetch); } } function throwIfSelectorIsNotId(selector, methodName) { if (!LocalCollection._selectorIsIdPerhapsAsObject(selector)) { throw new Meteor.Error(403, "Not permitted. Untrusted code may only " + methodName + " documents by ID."); } } ; // Determine if we are in a DDP method simulation function alreadyInSimulation() { var CurrentInvocation = DDP._CurrentMethodInvocation || // For backwards compatibility, as explained in this issue: // https://github.com/meteor/meteor/issues/8947 DDP._CurrentInvocation; var enclosing = CurrentInvocation.get(); return enclosing && enclosing.isSimulation; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// }}}}},{ "extensions": [ ".js", ".json" ] }); require("/node_modules/meteor/allow-deny/allow-deny.js"); /* Exports */ Package._define("allow-deny", { AllowDeny: AllowDeny }); })();