It’s probably no secret to you that I’m working on a new project called Inject Detect. As I mentioned last week, as a part of that project I need a way to intercept all MongoDB queries made by a Meteor application.
To figure out how to do this, we’ll need to spend a little time diving into the internals of Meteor to discover how it manages its MongoDB collections and drivers.
From there, we can create a Meteor package that intercepts all queries made against all MongoDB collections in an application.
Hold on tight, things are about to get heavy.
Exploring MongoInternals
One way of accomplishing our query interception goal is to monkey patch all of the query functions we care about, like find
, findOne
, remove
, udpate
, and upsert
.
To do that, we first need to find out where those functions are defined.
It turns out that the functions we’re looking for are defined on the prototype
of the MongoConnection
object which is declared in the mongo
Meteor package:
MongoConnection.prototype.find = function (collectionName, selector, options) {
...
};
When we instantiate a new Mongo.Collection
in our application, we’re actually invoking the MongoConnection
constructor.
So we want to monkey patch functions on MongoConnection
. Unfortunately, the MongoConnection
object isn’t exported by the mongo
package, so we can’t access it directly from our own code.
How do we get to it?
Thankfully, MongoConnection
is eventually assigned to MongoInternals.Connection
, and the MongoInternals
object is globally exported by the mongo
package.
This MongoInternals
object will be our entry-point for hooking into MongoDB queries at a very low level.
Monkey Patching MongoInternals
Since we know where to look, let’s get to work monkey patching our query functions.
Assuming we have a package already set up, the first thing we’ll do is import MongoInternals
from the mongo
Meteor package:
import { MongoInternals } from "meteor/mongo";
Let’s apply our first patch to find
. First, we’ll save the original reference to find
in a variable called _find
:
const _find = MongoInternals.Connection.prototype.find;
Now that we’ve saved off a reference to the original find
function, let’s override find
on the MongoInternals.Connection
prototype:
MongoInternals.Connection.prototype.find = function(collection, selector) {
console.log(`Querying "${collection}" with ${JSON.stringify(selector)}.`);
return _find.apply(this, arguments);
};
We’ve successfully monkey patched the find
function to print the collection and selector being queried before passing off the function call to the original find
function (_find
).
Let’s try it out in the shell:
> import "/imports/queryMonkeyPatcher"; // This is our monkey patching module
> Foo = new Mongo.Collection("foos");
> Foo.find({bar: "123"}).fetch();
Querying "foos" with {"bar": "123"}.
[{_id: "...", bar: "123"}]
As long as we import our code before we instantiate our collections, all calls to find
in those collections will be routed through our new find
function!
Now that we know our find
monkey patch works, we can go ahead and repeat the procedure for the rest of the query functions we’re interested in.
How is This Useful?
This kind of low-level query interception can be thought of as collection hooks on steroids.
Josh Owens uses this kind of low-level hooking to explain every query made by an application with his Mongo Explainer Meteor package.
Similarly, Inject Detect will make use of this query-hooking-foo by collecting information on all queries made by your application and sending it off to be inspected for potential NoSQL Injection attacks.
This kind of low level hooking is an incredibly powerful tool that can accomplish some amazing things if used correctly.