Improve hidden/protected property checks
- Fixes #1645 - do not apply hidden property check for data - Fixes #1648 - allow prohibitHiddenPropertiesInQuery to be set in model/ds
This commit is contained in:
parent
8ebb734a31
commit
8fa7c94605
113
lib/dao.js
113
lib/dao.js
|
@ -406,7 +406,7 @@ DataAccessObject.create = function(data, options, cb) {
|
||||||
obj.trigger('create', function(createDone) {
|
obj.trigger('create', function(createDone) {
|
||||||
obj.trigger('save', function(saveDone) {
|
obj.trigger('save', function(saveDone) {
|
||||||
var _idName = idName(Model);
|
var _idName = idName(Model);
|
||||||
var val = Model._sanitize(obj.toObject(true));
|
var val = Model._sanitizeData(obj.toObject(true));
|
||||||
function createCallback(err, id, rev) {
|
function createCallback(err, id, rev) {
|
||||||
if (id) {
|
if (id) {
|
||||||
obj.__data[_idName] = id;
|
obj.__data[_idName] = id;
|
||||||
|
@ -635,7 +635,7 @@ DataAccessObject.upsert = function(data, options, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function callConnector() {
|
function callConnector() {
|
||||||
update = Model._sanitize(update);
|
update = Model._sanitizeData(update);
|
||||||
context = {
|
context = {
|
||||||
Model: Model,
|
Model: Model,
|
||||||
where: ctx.where,
|
where: ctx.where,
|
||||||
|
@ -803,12 +803,9 @@ DataAccessObject.upsertWithWhere = function(where, data, options, cb) {
|
||||||
|
|
||||||
function callConnector() {
|
function callConnector() {
|
||||||
try {
|
try {
|
||||||
// Support an optional where object
|
ctx.where = Model._sanitizeQuery(ctx.where);
|
||||||
var normalizeUndefinedInQuery = Model._getSetting('normalizeUndefinedInQuery');
|
|
||||||
// alter configuration of how sanitizeQuery handles undefined values
|
|
||||||
ctx.where = Model._sanitize(ctx.where, {normalizeUndefinedInQuery: normalizeUndefinedInQuery});
|
|
||||||
ctx.where = Model._coerce(ctx.where, options);
|
ctx.where = Model._coerce(ctx.where, options);
|
||||||
update = Model._sanitize(update);
|
update = Model._sanitizeData(update);
|
||||||
update = Model._coerce(update, options);
|
update = Model._coerce(update, options);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return process.nextTick(function() {
|
return process.nextTick(function() {
|
||||||
|
@ -989,7 +986,7 @@ DataAccessObject.replaceOrCreate = function replaceOrCreate(data, options, cb) {
|
||||||
}, update, options);
|
}, update, options);
|
||||||
|
|
||||||
function callConnector() {
|
function callConnector() {
|
||||||
update = Model._sanitize(update);
|
update = Model._sanitizeData(update);
|
||||||
context = {
|
context = {
|
||||||
Model: Model,
|
Model: Model,
|
||||||
where: where,
|
where: where,
|
||||||
|
@ -1170,7 +1167,7 @@ DataAccessObject.findOrCreate = function findOrCreate(query, data, options, cb)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
data = Model._sanitize(data);
|
data = Model._sanitizeData(data);
|
||||||
var context = {
|
var context = {
|
||||||
Model: Model,
|
Model: Model,
|
||||||
where: query.where,
|
where: query.where,
|
||||||
|
@ -1456,24 +1453,28 @@ DataAccessObject.all = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get settings via hiarchical determiniation
|
* Get settings via hierarchical determination
|
||||||
*
|
*
|
||||||
* @param {String} key The setting key
|
* @param {String} key The setting key
|
||||||
*/
|
*/
|
||||||
DataAccessObject._getSetting = function(key) {
|
DataAccessObject._getSetting = function(key) {
|
||||||
// Check for settings in model
|
// Check for settings in model
|
||||||
var m = this.definition;
|
var m = this.definition;
|
||||||
if (m && m.settings && m.settings[key]) {
|
if (m && m.settings) {
|
||||||
return m.settings[key];
|
var val = m.settings[key];
|
||||||
|
if (val !== undefined) {
|
||||||
|
return m.settings[key];
|
||||||
|
}
|
||||||
|
// Fall back to datasource level
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for settings in connector
|
// Check for settings in connector
|
||||||
var ds = this.getDataSource();
|
var ds = this.getDataSource();
|
||||||
if (ds && ds.settings && ds.settings[key]) {
|
if (ds && ds.settings) {
|
||||||
return ds.settings[key];
|
return ds.settings[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
var operators = {
|
var operators = {
|
||||||
|
@ -1579,9 +1580,7 @@ DataAccessObject._normalize = function(filter, options) {
|
||||||
Object.keys(this.definition.properties), this.settings.strict);
|
Object.keys(this.definition.properties), this.settings.strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
var normalizeUndefinedInQuery = this._getSetting('normalizeUndefinedInQuery');
|
filter = this._sanitizeQuery(filter);
|
||||||
// alter configuration of how sanitizeQuery handles undefined values
|
|
||||||
filter = this._sanitize(filter, {normalizeUndefinedInQuery: normalizeUndefinedInQuery});
|
|
||||||
this._coerce(filter.where, options);
|
this._coerce(filter.where, options);
|
||||||
return filter;
|
return filter;
|
||||||
};
|
};
|
||||||
|
@ -1644,9 +1643,7 @@ function coerceArray(val) {
|
||||||
return arrayVal;
|
return arrayVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataAccessObject._getHiddenProperties = function() {
|
function _normalizeAsArray(result) {
|
||||||
var settings = this.definition.settings || {};
|
|
||||||
var result = settings.hiddenProperties || settings.hidden || [];
|
|
||||||
if (typeof result === 'string') {
|
if (typeof result === 'string') {
|
||||||
result = [result];
|
result = [result];
|
||||||
}
|
}
|
||||||
|
@ -1655,35 +1652,59 @@ DataAccessObject._getHiddenProperties = function() {
|
||||||
} else {
|
} else {
|
||||||
// See https://github.com/strongloop/loopback-datasource-juggler/issues/1646
|
// See https://github.com/strongloop/loopback-datasource-juggler/issues/1646
|
||||||
// `ModelBaseClass` normalize the properties to an object such as `{secret: true}`
|
// `ModelBaseClass` normalize the properties to an object such as `{secret: true}`
|
||||||
return Object.keys(result);
|
var keys = [];
|
||||||
|
for (var k in result) {
|
||||||
|
if (result[k]) keys.push(k);
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DataAccessObject._getHiddenProperties = function() {
|
||||||
|
var settings = this.definition.settings || {};
|
||||||
|
var result = settings.hiddenProperties || settings.hidden || [];
|
||||||
|
return _normalizeAsArray(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
DataAccessObject._getProtectedProperties = function() {
|
DataAccessObject._getProtectedProperties = function() {
|
||||||
var settings = this.definition.settings || {};
|
var settings = this.definition.settings || {};
|
||||||
var result = settings.protectedProperties || settings.protected || [];
|
var result = settings.protectedProperties || settings.protected || [];
|
||||||
if (typeof result === 'string') {
|
return _normalizeAsArray(result);
|
||||||
result = [result];
|
|
||||||
}
|
|
||||||
if (Array.isArray(result)) {
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
// See https://github.com/strongloop/loopback-datasource-juggler/issues/1646
|
|
||||||
// `ModelBaseClass` normalize the properties to an object such as `{secret: true}`
|
|
||||||
return Object.keys(result);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DataAccessObject._sanitize = function(query, options) {
|
DataAccessObject._sanitizeQuery = function(query, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
|
// Get settings to normalize `undefined` values
|
||||||
|
var normalizeUndefinedInQuery = this._getSetting('normalizeUndefinedInQuery');
|
||||||
|
// Get setting to prohibit hidden/protected properties in query
|
||||||
|
var prohibitHiddenPropertiesInQuery = this._getSetting('prohibitHiddenPropertiesInQuery');
|
||||||
|
if (prohibitHiddenPropertiesInQuery == null) {
|
||||||
|
// By default, hidden properties are prohibited in query
|
||||||
|
prohibitHiddenPropertiesInQuery = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prohibitHiddenPropertiesInQuery)
|
||||||
|
console.log(prohibitHiddenPropertiesInQuery);
|
||||||
|
|
||||||
|
var prohibitedKeys = [];
|
||||||
// Check violation of keys
|
// Check violation of keys
|
||||||
var prohibitedKeys = this._getHiddenProperties();
|
if (prohibitHiddenPropertiesInQuery) {
|
||||||
if (options.excludeProtectedProperties) {
|
prohibitedKeys = this._getHiddenProperties();
|
||||||
prohibitedKeys = prohibitedKeys.concat(this._getProtectedProperties());
|
if (options.prohibitProtectedPropertiesInQuery) {
|
||||||
|
prohibitedKeys = prohibitedKeys.concat(this._getProtectedProperties());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return sanitizeQueryOrData(query,
|
return sanitizeQueryOrData(query,
|
||||||
Object.assign({prohibitedKeys: prohibitedKeys}, options));
|
Object.assign({
|
||||||
|
prohibitedKeys: prohibitedKeys,
|
||||||
|
normalizeUndefinedInQuery: normalizeUndefinedInQuery,
|
||||||
|
}, options));
|
||||||
|
};
|
||||||
|
|
||||||
|
DataAccessObject._sanitizeData = function(data, options) {
|
||||||
|
options = options || {};
|
||||||
|
return sanitizeQueryOrData(data, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -2335,9 +2356,8 @@ DataAccessObject.destroyAll = function destroyAll(where, options, cb) {
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
// Support an optional where object
|
// Support an optional where object
|
||||||
var normalizeUndefinedInQuery = Model._getSetting('normalizeUndefinedInQuery');
|
|
||||||
// alter configuration of how sanitizeQuery handles undefined values
|
// alter configuration of how sanitizeQuery handles undefined values
|
||||||
where = Model._sanitize(where, {normalizeUndefinedInQuery: normalizeUndefinedInQuery});
|
where = Model._sanitizeQuery(where);
|
||||||
where = Model._coerce(where, options);
|
where = Model._coerce(where, options);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return process.nextTick(function() {
|
return process.nextTick(function() {
|
||||||
|
@ -2490,9 +2510,8 @@ DataAccessObject.count = function(where, options, cb) {
|
||||||
where = query.where;
|
where = query.where;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var normalizeUndefinedInQuery = Model._getSetting('normalizeUndefinedInQuery');
|
|
||||||
// alter configuration of how sanitizeQuery handles undefined values
|
// alter configuration of how sanitizeQuery handles undefined values
|
||||||
where = Model._sanitize(where, {normalizeUndefinedInQuery: normalizeUndefinedInQuery});
|
where = Model._sanitizeQuery(where);
|
||||||
where = this._coerce(where, options);
|
where = this._coerce(where, options);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
process.nextTick(function() {
|
process.nextTick(function() {
|
||||||
|
@ -2598,7 +2617,7 @@ DataAccessObject.prototype.save = function(options, cb) {
|
||||||
function save() {
|
function save() {
|
||||||
inst.trigger('save', function(saveDone) {
|
inst.trigger('save', function(saveDone) {
|
||||||
inst.trigger('update', function(updateDone) {
|
inst.trigger('update', function(updateDone) {
|
||||||
data = Model._sanitize(data);
|
data = Model._sanitizeData(data);
|
||||||
function saveCallback(err, unusedData, result) {
|
function saveCallback(err, unusedData, result) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(err, inst);
|
return cb(err, inst);
|
||||||
|
@ -2784,12 +2803,10 @@ DataAccessObject.updateAll = function(where, data, options, cb) {
|
||||||
|
|
||||||
function doUpdate(where, data) {
|
function doUpdate(where, data) {
|
||||||
try {
|
try {
|
||||||
// Support an optional where object
|
|
||||||
var normalizeUndefinedInQuery = Model._getSetting('normalizeUndefinedInQuery');
|
|
||||||
// alter configuration of how sanitizeQuery handles undefined values
|
// alter configuration of how sanitizeQuery handles undefined values
|
||||||
where = Model._sanitize(where, {normalizeUndefinedInQuery: normalizeUndefinedInQuery});
|
where = Model._sanitizeQuery(where);
|
||||||
where = Model._coerce(where, options);
|
where = Model._coerce(where, options);
|
||||||
data = Model._sanitize(data);
|
data = Model._sanitizeData(data);
|
||||||
data = Model._coerce(data, options);
|
data = Model._coerce(data, options);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return process.nextTick(function() {
|
return process.nextTick(function() {
|
||||||
|
@ -3122,7 +3139,7 @@ DataAccessObject.replaceById = function(id, data, options, cb) {
|
||||||
|
|
||||||
function validateAndCallConnector(err, data) {
|
function validateAndCallConnector(err, data) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
data = Model._sanitize(data);
|
data = Model._sanitizeData(data);
|
||||||
// update instance's properties
|
// update instance's properties
|
||||||
inst.setAttributes(data);
|
inst.setAttributes(data);
|
||||||
|
|
||||||
|
@ -3274,7 +3291,7 @@ function(data, options, cb) {
|
||||||
if (data instanceof Model) {
|
if (data instanceof Model) {
|
||||||
data = data.toObject(false);
|
data = data.toObject(false);
|
||||||
}
|
}
|
||||||
data = Model._sanitize(data);
|
data = Model._sanitizeData(data);
|
||||||
|
|
||||||
// Make sure id(s) cannot be changed
|
// Make sure id(s) cannot be changed
|
||||||
var idNames = Model.definition.idNames();
|
var idNames = Model.definition.idNames();
|
||||||
|
@ -3347,7 +3364,7 @@ function(data, options, cb) {
|
||||||
inst.trigger('update', function(done) {
|
inst.trigger('update', function(done) {
|
||||||
copyData(data, inst);
|
copyData(data, inst);
|
||||||
var typedData = convertSubsetOfPropertiesByType(inst, data);
|
var typedData = convertSubsetOfPropertiesByType(inst, data);
|
||||||
context.data = Model._sanitize(typedData);
|
context.data = Model._sanitizeData(typedData);
|
||||||
|
|
||||||
// Depending on the connector, the database response can
|
// Depending on the connector, the database response can
|
||||||
// contain information about the updated record(s), but also
|
// contain information about the updated record(s), but also
|
||||||
|
|
|
@ -205,7 +205,7 @@ Inclusion.include = function(objects, include, options, cb) {
|
||||||
*/
|
*/
|
||||||
function findWithForeignKeysByPage(model, filter, fkName, pageSize, options, cb) {
|
function findWithForeignKeysByPage(model, filter, fkName, pageSize, options, cb) {
|
||||||
try {
|
try {
|
||||||
model._sanitize(filter.where, {excludeProtectedProperties: true});
|
model._sanitizeQuery(filter.where, {prohibitProtectedPropertiesInQuery: true});
|
||||||
model._coerce(filter.where);
|
model._coerce(filter.where);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return cb(e);
|
return cb(e);
|
||||||
|
|
|
@ -353,6 +353,24 @@ describe('ModelDefinition class', function() {
|
||||||
where: {and: [{secret: 'guess'}]},
|
where: {and: [{secret: 'guess'}]},
|
||||||
}).then(assertHiddenPropertyIsIgnored);
|
}).then(assertHiddenPropertyIsIgnored);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be allowed for update', function() {
|
||||||
|
return Child.update({name: 'childA'}, {secret: 'new-secret'}).then(
|
||||||
|
function(result) {
|
||||||
|
result.count.should.equal(1);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be allowed if prohibitHiddenPropertiesInQuery is `false`', function() {
|
||||||
|
Child.definition.settings.prohibitHiddenPropertiesInQuery = false;
|
||||||
|
return Child.find({
|
||||||
|
where: {secret: 'guess'},
|
||||||
|
}).then(function(children) {
|
||||||
|
children.length.should.equal(1);
|
||||||
|
children[0].secret.should.equal('guess');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with hidden object', function() {
|
describe('with hidden object', function() {
|
||||||
|
|
Loading…
Reference in New Issue