Files
2025-02-26 14:49:25 +07:00

567 lines
22 KiB
JavaScript

//////////////////////////////////////////////////////////////////////////
// //
// 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
});
})();