Skip to content

Commit

Permalink
Add meta flag for 'unsafe' queries
Browse files Browse the repository at this point in the history
This closes issue #1489
  • Loading branch information
mdconaway committed Aug 26, 2017
1 parent 3345b27 commit 804a8d9
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 19 deletions.
26 changes: 22 additions & 4 deletions lib/waterline/utils/query/forge-stage-three-query.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ module.exports = function forgeStageThreeQuery(options) {
var identity = options.identity;
var transformer = options.transformer;
var originalModels = options.originalModels;

var isUnsafeQuery = s3Q.meta && s3Q.meta.unsafe;

// ╔═╗╦╔╗╔╔╦╗ ┌┬┐┌─┐┌┬┐┌─┐┬
// ╠╣ ║║║║ ║║ ││││ │ ││├┤ │
Expand Down Expand Up @@ -198,7 +198,13 @@ module.exports = function forgeStageThreeQuery(options) {
var sort = {};
var attrName = _.first(_.keys(sortClause));
var sortDirection = sortClause[attrName];
var columnName = model.schema[attrName].columnName;
var schemaAttr = model.schema[attrName];
if(isUnsafeQuery && !schemaAttr) {
schemaAttr = {
columnName: attrName
};
}
var columnName = schemaAttr.columnName;
sort[columnName] = sortDirection;
return sort;
});
Expand Down Expand Up @@ -245,7 +251,13 @@ module.exports = function forgeStageThreeQuery(options) {
var sort = {};
var attrName = _.first(_.keys(sortClause));
var sortDirection = sortClause[attrName];
var columnName = model.schema[attrName].columnName;
var schemaAttr = model.schema[attrName];
if(isUnsafeQuery && !schemaAttr) {
schemaAttr = {
columnName: attrName
};
}
var columnName = schemaAttr.columnName;
sort[columnName] = sortDirection;
return sort;
});
Expand Down Expand Up @@ -573,7 +585,13 @@ module.exports = function forgeStageThreeQuery(options) {
var sort = {};
var attrName = _.first(_.keys(sortClause));
var sortDirection = sortClause[attrName];
var columnName = model.schema[attrName].columnName;
var schemaAttr = model.schema[attrName];
if(isUnsafeQuery && !schemaAttr) {
schemaAttr = {
columnName: attrName
};
}
var columnName = schemaAttr.columnName;
sort[columnName] = sortDirection;
return sort;
});
Expand Down
2 changes: 1 addition & 1 deletion lib/waterline/utils/query/forge-stage-two-query.js
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ module.exports = function forgeStageTwoQuery(query, orm) {
// ╝╚╝╚═╝╩╚═╩ ╩╩ ╩╩═╝╩╚═╝╚═╝ └┘ ╚╝ ╩ ╩╩═╝╩═╩╝╩ ╩ ╩ ╚═╝
// Validate and normalize the provided `criteria`.
try {
query.criteria = normalizeCriteria(query.criteria, query.using, orm);
query.criteria = normalizeCriteria(query.criteria, query.using, orm, query.meta);
} catch (e) {
switch (e.code) {

Expand Down
8 changes: 6 additions & 2 deletions lib/waterline/utils/query/private/is-valid-attribute-name.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions lib/waterline/utils/query/private/normalize-constraint.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ var MODIFIER_KINDS = {
* @param {Ref} orm
* The Waterline ORM instance.
* > Useful for accessing the model definitions.
*
* @param {Dictionary} meta
* The Waterline meta-data for this query
* > Useful for propagating query options to low-level functions
* ------------------------------------------------------------------------------------------
* @returns {Dictionary|String|Number|Boolean|JSON}
* The constraint (potentially the same ref), guaranteed to be valid for a stage 2 query.
Expand All @@ -88,7 +92,7 @@ var MODIFIER_KINDS = {
* ------------------------------------------------------------------------------------------
*/

module.exports = function normalizeConstraint (constraint, attrName, modelIdentity, orm){
module.exports = function normalizeConstraint (constraint, attrName, modelIdentity, orm, meta){
if (_.isUndefined(constraint)) {
throw new Error('Consistency violation: The internal normalizeConstraint() utility must always be called with a first argument (the constraint to normalize). But instead, got: '+util.inspect(constraint, {depth:5})+'');
}
Expand All @@ -101,7 +105,7 @@ module.exports = function normalizeConstraint (constraint, attrName, modelIdenti

// Look up the Waterline model for this query.
var WLModel = getModel(modelIdentity, orm);

var isUnsafeQuery = meta && meta.unsafe;
// Before we look at the constraint, we'll check the key to be sure it is valid for this model.
// (in the process, we look up the expected type for the corresponding attribute,
// so that we have something to validate against)
Expand Down Expand Up @@ -137,7 +141,7 @@ module.exports = function normalizeConstraint (constraint, attrName, modelIdenti
else if (WLModel.hasSchema === false) {

// Make sure this is at least a valid name for a Waterline attribute.
if (!isValidAttributeName(attrName)) {
if (!isValidAttributeName(attrName, isUnsafeQuery)) {
throw flaverr('E_CONSTRAINT_NOT_USABLE', new Error(
'`'+attrName+'` is not a valid name for an attribute in Waterline. '+
'Even though this model (`'+modelIdentity+'`) declares `schema: false`, '+
Expand Down
12 changes: 8 additions & 4 deletions lib/waterline/utils/query/private/normalize-criteria.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ var NAMES_OF_RECOGNIZED_CLAUSES = ['where', 'limit', 'skip', 'sort', 'select', '
* @param {Ref} orm
* The Waterline ORM instance.
* > Useful for accessing the model definitions.
*
*
* @param {Dictionary} meta
* The Waterline meta-data for this query
* > Useful for propagating query options to low-level functions
*
* --
*
* @returns {Dictionary}
Expand All @@ -85,7 +89,7 @@ var NAMES_OF_RECOGNIZED_CLAUSES = ['where', 'limit', 'skip', 'sort', 'select', '
*
* @throws {Error} If anything else unexpected occurs.
*/
module.exports = function normalizeCriteria(criteria, modelIdentity, orm) {
module.exports = function normalizeCriteria(criteria, modelIdentity, orm, meta) {

// Sanity checks.
// > These are just some basic, initial usage assertions to help catch
Expand Down Expand Up @@ -471,7 +475,7 @@ module.exports = function normalizeCriteria(criteria, modelIdentity, orm) {
//

try {
criteria.where = normalizeWhereClause(criteria.where, modelIdentity, orm);
criteria.where = normalizeWhereClause(criteria.where, modelIdentity, orm, meta);
} catch (e) {
switch (e.code) {

Expand Down Expand Up @@ -636,7 +640,7 @@ module.exports = function normalizeCriteria(criteria, modelIdentity, orm) {
//
// Validate/normalize `sort` clause.
try {
criteria.sort = normalizeSortClause(criteria.sort, modelIdentity, orm);
criteria.sort = normalizeSortClause(criteria.sort, modelIdentity, orm, meta);
} catch (e) {
switch (e.code) {

Expand Down
10 changes: 7 additions & 3 deletions lib/waterline/utils/query/private/normalize-sort-clause.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ var isValidAttributeName = require('./is-valid-attribute-name');
* @param {Ref} orm
* The Waterline ORM instance.
* > Useful for accessing the model definitions.
*
* @param {Dictionary} meta
* The Waterline meta-data for this query
* > Useful for propagating query options to low-level functions
* --
*
* @returns {Array}
Expand All @@ -50,12 +54,12 @@ var isValidAttributeName = require('./is-valid-attribute-name');
* @throws {Error} If anything else unexpected occurs.
*/

module.exports = function normalizeSortClause(sortClause, modelIdentity, orm) {
module.exports = function normalizeSortClause(sortClause, modelIdentity, orm, meta) {

// Look up the Waterline model for this query.
// > This is so that we can reference the original model definition.
var WLModel = getModel(modelIdentity, orm);

var isUnsafeQuery = meta && meta.unsafe;
// ╔═╗╔═╗╔╦╗╔═╗╔═╗╔╦╗╦╔╗ ╦╦ ╦╔╦╗╦ ╦
// ║ ║ ║║║║╠═╝╠═╣ ║ ║╠╩╗║║ ║ ║ ╚╦╝
// ╚═╝╚═╝╩ ╩╩ ╩ ╩ ╩ ╩╚═╝╩╩═╝╩ ╩ ╩
Expand Down Expand Up @@ -301,7 +305,7 @@ module.exports = function normalizeSortClause(sortClause, modelIdentity, orm) {
else if (WLModel.hasSchema === false) {

// Make sure this is at least a valid name for a Waterline attribute.
if (!isValidAttributeName(sortByKey)) {
if (!isValidAttributeName(sortByKey, isUnsafeQuery)) {
throw flaverr('E_SORT_CLAUSE_UNUSABLE', new Error(
'The `sort` clause in the provided criteria is invalid, because, although it '+
'is an array, one of its items (aka comparator directives) is problematic. '+
Expand Down
8 changes: 6 additions & 2 deletions lib/waterline/utils/query/private/normalize-where-clause.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ var PREDICATE_OPERATOR_KINDS = [
* The Waterline ORM instance.
* > Useful for accessing the model definitions.
*
* @param {Dictionary} meta
* The Waterline meta-data for this query
* > Useful for propagating query options to low-level functions
*
* ------------------------------------------------------------------------------------------
* @returns {Dictionary}
* The successfully-normalized `where` clause, ready for use in a stage 2 query.
Expand All @@ -63,7 +67,7 @@ var PREDICATE_OPERATOR_KINDS = [
*
* @throws {Error} If anything else unexpected occurs.
*/
module.exports = function normalizeWhereClause(whereClause, modelIdentity, orm) {
module.exports = function normalizeWhereClause(whereClause, modelIdentity, orm, meta) {

// Look up the Waterline model for this query.
// > This is so that we can reference the original model definition.
Expand Down Expand Up @@ -466,7 +470,7 @@ module.exports = function normalizeWhereClause(whereClause, modelIdentity, orm)
// Normalize the constraint itself.
// (note that this also checks the key -- i.e. the attr name)
try {
branch[soleBranchKey] = normalizeConstraint(branch[soleBranchKey], soleBranchKey, modelIdentity, orm);
branch[soleBranchKey] = normalizeConstraint(branch[soleBranchKey], soleBranchKey, modelIdentity, orm, meta);
} catch (e) {
switch (e.code) {

Expand Down
110 changes: 110 additions & 0 deletions test/unit/query/query.find.unsafe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
var assert = require('assert');
var _ = require('@sailshq/lodash');
var Waterline = require('../../../lib/waterline');

describe('Collection Query ::', function() {
describe('.find()', function() {
describe('with meta unsafe flag', function(){
var query;

before(function(done) {
var waterline = new Waterline();
var Model = Waterline.Model.extend({
identity: 'user',
connection: 'foo',
primaryKey: 'id',
schema: false,
attributes: {
id: {
type: 'number'
},
name: {
type: 'string',
defaultsTo: 'Foo Bar'
},
nested: {
type: 'json'
}
}
});

waterline.registerModel(Model);

// Fixture Adapter Def
var adapterDef = { find: function(con, query, cb) { return cb(null, [{id: 1, criteria: query.criteria}]); }};

var connections = {
'foo': {
adapter: 'foobar'
}
};

waterline.initialize({ adapters: { foobar: adapterDef }, datastores: connections }, function(err, orm) {
if(err) {
return done(err);
}
query = orm.collections.user;
return done();
});
});

it('should allow meta to be optional', function(done) {
query.find({}, function(err) {
if(err) {
return done(err);
}

return done();
});
});

it('should block a query not explicitly unsafe', function(done) {
query.find({
'nested.key': 'foobar'
}, function(err) {
if(err) {
return done();
}

return done(new Error('Unsafe query allowed through by default!'));
});
});

it('should allow a mongo-style query to pass through to the adapter with meta.unsafe', function(done) {
query.find()
.where({ 'nested.key': 'Foo Bar' })
.sort([{ 'nested.key': 'desc' }])
.meta({unsafe: true})
.exec(function(err, results) {
if (err) {
return done(err);
}

assert(_.isArray(results));
assert.equal(results[0].criteria.where['nested.key'], 'Foo Bar');
assert.equal(results[0].criteria.sort[0]['nested.key'], 'DESC');

return done();
});
});

it('should still normalize mongo-style waterline queries with meta.unsafe', function(done) {
query.find()
.where({ 'nested.key': ['Foo Bar'] })
.sort([{ 'nested.key': 'desc' }])
.meta({unsafe: true})
.exec(function(err, results) {
if (err) {
return done(err);
}

assert(_.isArray(results));
assert.equal(results[0].criteria.where['nested.key'].in[0], 'Foo Bar');
assert.equal(results[0].criteria.sort[0]['nested.key'], 'DESC');

return done();
});
});
});
});
});
Loading

0 comments on commit 804a8d9

Please sign in to comment.