Compare commits

...
15 Commits
Author SHA1 Message Date
Florent Vilmart a45d1848db version 2.3.6 (#3574)
* version 2.3.6

* Update CHANGELOG.md
2017-02-27 19:10:06 +02:00
Greenkeeper 6381e15e6d chore(package): update pg-promise to version 5.6.2 (#3573)
https://greenkeeper.io/
2017-02-27 11:45:43 -05:00
Jack Wearden 73195627c0 Alleviate SERVER-13732 on all top level filters (#3564)
In a prior commit, improvements were made to the addition of `_rperm`
in the case of `$or` queries, to avoid MongoDB bug SERVER-13732.

As the vast majority of $or queries previously hit this bug due to the
presence of `_rperm` on most Parse queries), the present solution
avoids the bug and improves query performance in most cases.

However, it's still possible for clients to supply their own queries
which hit that bug, such as those with `_created_at` or `_updated_at`
filters, or their own properties from their data model.

This commit makes the logic currently present for `_rperm` available
to all top level filters that exist alongside an $or query, meaning
SERVER-13732 should be avoided in all cases where keys at the top and
inner levels do not have name clashes.

- https://github.com/ParsePlatform/parse-server/pull/3476
- https://jira.mongodb.org/browse/SERVER-13732
2017-02-26 13:37:33 -05:00
Greenkeeper 053bbefda9 chore(package): update pg-promise to version 5.6.1 (#3572)
https://greenkeeper.io/
2017-02-26 13:34:19 -05:00
Paulo Vítor S Reis 032d5e55ca Logger MaskSentive just for strings and array string items (#3571) 2017-02-26 13:34:03 -05:00
shaukatnamal fe5947ce4c chore(package): update eslint to version 3.16.1 (#3563)
https://greenkeeper.io/
2017-02-24 16:18:57 -08:00
Bhaskar Reddy Yasa 0e900cbefd allow flow through to passwordPolicy in case of empty ('') password (#3560) 2017-02-24 07:21:50 -05:00
Florent Vilmart 41358d2226 Adds ability to pass a middleware to CLI for instrumentation (#3554)
* Adds ability to pass a middleware to CLI for instrumentation

* Adds readme
2017-02-22 23:42:21 +02:00
Greenkeeper 73260897cd chore(package): update pg-promise to version 5.5.8 (#3551)
https://greenkeeper.io/
2017-02-21 14:28:12 -08:00
Florent Vilmart d3e6c0dea3 Adds test for #3544 (#3545) 2017-02-21 15:06:45 -05:00
Florent Vilmart b4e27e1160 Adds testing safeguard on lib (#3537) 2017-02-20 22:45:45 +02:00
Greenkeeper 79b0c790f0 chore(package): update uws to version 0.13.0 (#3542)
https://greenkeeper.io/
2017-02-20 12:26:44 -08:00
Greenkeeper 64f608ec9f chore(package): update ws to version 2.1.0 (#3526)
https://greenkeeper.io/
2017-02-20 12:25:39 -08:00
Tyler Brock 67260b5fe0 Factor out checking for triggers/liveQuery in rest.js (#3539) 2017-02-20 12:25:25 -08:00
Florent Vilmart 411daf4d41 Release 2.3.5 2017-02-19 11:34:42 -05:00
14 changed files with 183 additions and 27 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ addons:
apt_packages:
- postgresql-9.5-postgis-2.3
before_script:
- ls -al "$HOME/.mongodb/versions"
- node -e 'require("./lib/index.js")'
- psql -c 'create database parse_server_postgres_adapter_test_database;' -U postgres
- psql -c 'CREATE EXTENSION postgis;' -U postgres -d parse_server_postgres_adapter_test_database
- psql -c 'CREATE EXTENSION postgis_topology;' -U postgres -d parse_server_postgres_adapter_test_database
+19 -2
View File
@@ -1,7 +1,24 @@
## Parse Server Changelog
### 2.3.4
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.3...2.3.4)
### 2.3.6
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.5...2.3.6)
#### Improvements
* Adds support for injecting a middleware for instumentation in the CLI, thanks to [Florent Vilmart](https://github.com/flovilmart)
* Alleviate mongodb bug with $or queries [SERVER-13732](https://jira.mongodb.org/browse/SERVER-13732), thanks to [Jack Wearden](https://github.com/NotBobTheBuilder)
#### Bug Fixes
* Fix issue affecting password policy and empty passwords, thanks to [Bhaskar Reddy Yasa](https://github.com/bhaskaryasa)
* Fix issue when logging url in non string objects, thanks to [Paulo Vítor S Reis](https://github.com/paulovitin)
#### Dependencies updates:
* [ws@2.1.0](https://npmjs.com/package/ws)
* [uws@0.13.0](https://npmjs.com/package/uws)
* [pg-promise@5.6.2](https://npmjs.com/package/pg-promise)
### 2.3.5
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.3...2.3.5)
#### Bug Fixes
* Allow empty client key
+1
View File
@@ -217,6 +217,7 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
* `accountLockout` - Lock account when a malicious user is attempting to determine an account password by trial and error.
* `passwordPolicy` - Optional password policy rules to enforce.
* `customPages` - A hash with urls to override email verification links, password reset links and specify frame url for masking user-facing pages. Available keys: `parseFrameURL`, `invalidLink`, `choosePassword`, `passwordResetSuccess`, `verifyEmailSuccess`.
* `middleware` - (CLI only), a module name, function that is an express middleware. When using the CLI, the express app will load it just **before** mounting parse-server on the mount path. This option is useful for injecting a monitoring middleware.
##### Logging
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "parse-server",
"version": "2.3.4",
"version": "2.3.6",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"repository": {
@@ -34,14 +34,14 @@
"parse-server-push-adapter": "1.2.0",
"parse-server-s3-adapter": "1.0.6",
"parse-server-simple-mailgun-adapter": "1.0.0",
"pg-promise": "5.5.7",
"pg-promise": "5.6.2",
"redis": "2.6.5",
"request": "2.79.0",
"semver": "5.2.0",
"tv4": "1.2.7",
"winston": "2.3.1",
"winston-daily-rotate-file": "1.4.4",
"ws": "2.0.3"
"ws": "2.1.0"
},
"devDependencies": {
@@ -56,7 +56,7 @@
"bcrypt-nodejs": "0.0.3",
"cross-env": "3.1.4",
"deep-diff": "0.3.4",
"eslint": "^3.10.2",
"eslint": "^3.16.1",
"eslint-plugin-flowtype": "^2.25.0",
"gaze": "1.1.1",
"istanbul": "1.0.0-alpha.1",
@@ -69,7 +69,7 @@
"scripts": {
"dev": "npm run build && node bin/dev",
"lint": "eslint --cache ./",
"build": "babel src/ -d lib/",
"build": "babel src/ -d lib/ --copy-files",
"pretest": "npm run lint",
"test": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 NODE_ENV=test TESTING=1 $COVERAGE_OPTION jasmine",
"test:win": "cross-env MONGODB_VERSION=${MONGODB_VERSION:=3.2.6} MONGODB_STORAGE_ENGINE=mmapv1 NODE_ENV=test TESTING=1 jasmine",
@@ -86,6 +86,6 @@
},
"optionalDependencies": {
"bcrypt": "1.0.2",
"uws": "^0.12.0"
"uws": "^0.13.0"
}
}
+25
View File
@@ -3,6 +3,7 @@ var loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
var FilesAdapter = require("parse-server-fs-adapter").default;
var S3Adapter = require("parse-server-s3-adapter").default;
var ParsePushAdapter = require("parse-server-push-adapter").default;
const Config = require('../src/Config');
describe("AdapterLoader", ()=>{
@@ -118,6 +119,30 @@ describe("AdapterLoader", ()=>{
done();
});
it("should load custom push adapter from string (#3544)", (done) => {
var adapterPath = require('path').resolve("./spec/MockPushAdapter");
var options = {
ios: {
bundleId: 'bundle.id'
}
}
const pushAdapterOptions = {
adapter: adapterPath,
options
};
expect(() => {
reconfigureServer({
push: pushAdapterOptions,
}).then(() => {
const config = new Config(Parse.applicationId);
const pushAdapter = config.pushWorker.adapter;
expect(pushAdapter.getValidPushTypes()).toEqual(['ios']);
expect(pushAdapter.options).toEqual(pushAdapterOptions);
done();
});
}).not.toThrow();
});
it("should load S3Adapter from direct passing", (done) => {
var s3Adapter = new S3Adapter("key", "secret", "bucket")
expect(() => {
+28
View File
@@ -0,0 +1,28 @@
var DatabaseController = require('../src/Controllers/DatabaseController.js');
var validateQuery = DatabaseController._validateQuery;
describe('DatabaseController', function() {
describe('validateQuery', function() {
it('should restructure simple cases of SERVER-13732', (done) => {
var query = {$or: [{a: 1}, {a: 2}], _rperm: {$in: ['a', 'b']}, foo: 3};
validateQuery(query);
expect(query).toEqual({$or: [{a: 1, _rperm: {$in: ['a', 'b']}, foo: 3},
{a: 2, _rperm: {$in: ['a', 'b']}, foo: 3}]});
done();
});
it('should reject invalid queries', (done) => {
expect(() => validateQuery({$or: {'a': 1}})).toThrow();
done();
});
it('should accept valid queries', (done) => {
expect(() => validateQuery({$or: [{'a': 1}, {'b': 2}]})).not.toThrow();
done();
});
});
});
+9
View File
@@ -0,0 +1,9 @@
module.exports = function(options) {
return {
options: options,
send: function() {},
getValidPushTypes: function() {
return Object.keys(options.options);
}
};
};
+22
View File
@@ -219,6 +219,28 @@ describe("Password Policy: ", () => {
})
});
it('signup should fail if password is empty', (done) => {
const user = new Parse.User();
reconfigureServer({
appName: 'passwordPolicy',
passwordPolicy: {
validatorPattern: "^.{8,}" // password should contain at least 8 char
},
publicServerURL: "http://localhost:8378/1"
}).then(() => {
user.setUsername("user1");
user.setPassword("");
user.set('email', 'user1@parse.com');
user.signUp().then(() => {
fail('Should have failed as password does not conform to the policy.');
done();
}).catch((error) => {
expect(error.message).toEqual('Cannot sign up user with an empty password.');
done();
});
})
});
it('signup should succeed if password conforms to the policy enforced using validatorPattern', (done) => {
const user = new Parse.User();
reconfigureServer({
+27 -8
View File
@@ -18,14 +18,7 @@ function addWriteACL(query, acl) {
function addReadACL(query, acl) {
const newQuery = _.cloneDeep(query);
//Can't be any existing '_rperm' query, we don't allow client queries on that, no need to $and
if (newQuery.hasOwnProperty('$or')) {
newQuery.$or = newQuery.$or.map(function(qobj) {
qobj._rperm = {'$in' : [null, '*', ...acl]};
return qobj;
});
} else {
newQuery._rperm = { "$in" : [null, "*", ...acl]};
}
newQuery._rperm = {"$in": [null, "*", ...acl]};
return newQuery;
}
@@ -63,6 +56,30 @@ const validateQuery = query => {
if (query.$or) {
if (query.$or instanceof Array) {
query.$or.forEach(validateQuery);
/* In MongoDB, $or queries which are not alone at the top level of the
* query can not make efficient use of indexes due to a long standing
* bug known as SERVER-13732.
*
* This block restructures queries in which $or is not the sole top
* level element by moving all other top-level predicates inside every
* subdocument of the $or predicate, allowing MongoDB's query planner
* to make full use of the most relevant indexes.
*
* EG: {$or: [{a: 1}, {a: 2}], b: 2}
* Becomes: {$or: [{a: 1, b: 2}, {a: 2, b: 2}]}
*
* https://jira.mongodb.org/browse/SERVER-13732
*/
Object.keys(query).forEach(key => {
const noCollisions = !query.$or.some(subq => subq.hasOwnProperty(key))
if (key != '$or' && noCollisions) {
query.$or.forEach(subquery => {
subquery[key] = query[key];
});
delete query[key];
}
});
} else {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Bad $or format - use an array value.');
}
@@ -919,4 +936,6 @@ function joinTableName(className, key) {
return `_Join:${key}:${className}`;
}
// Expose validateQuery for tests
DatabaseController._validateQuery = validateQuery;
module.exports = DatabaseController;
+12 -1
View File
@@ -41,7 +41,18 @@ export class LoggerController extends AdaptableController {
// check the url
if (e.url) {
e.url = this.maskSensitiveUrl(e.url);
// for strings
if (typeof e.url === 'string') {
e.url = this.maskSensitiveUrl(e.url);
} else if (Array.isArray(e.url)) { // for strings in array
e.url = e.url.map(item => {
if (typeof item === 'string') {
return this.maskSensitiveUrl(item);
}
return item;
});
}
}
if (e.body) {
+1 -1
View File
@@ -366,7 +366,7 @@ RestWrite.prototype.transformUser = function() {
return promise.then(() => {
// Transform the password
if (!this.data.password) {
if (this.data.password === undefined) { // ignore only if undefined. should proceed if empty ('')
return Promise.resolve();
}
+3
View File
@@ -249,4 +249,7 @@ export default {
help: "Live query server configuration options (will start the liveQuery server)",
action: objectParser
},
"middleware": {
help: "middleware for express server, can be string or function"
}
};
+13 -1
View File
@@ -5,6 +5,7 @@ import definitions from './definitions/parse-server';
import cluster from 'cluster';
import os from 'os';
import runner from './utils/runner';
const path = require("path");
const help = function(){
console.log(' Get Started guide:');
@@ -30,9 +31,20 @@ const help = function(){
function startServer(options, callback) {
const app = express();
if (options.middleware) {
let middleware;
if (typeof options.middleware == 'function') {
middleware = options.middleware;
} if (typeof options.middleware == 'string') {
middleware = require(path.resolve(process.cwd(), options.middleware));
} else {
throw "middleware should be a string or a function";
}
app.use(middleware);
}
const api = new ParseServer(options);
const sockets = {};
app.use(options.mountPath, api);
const server = app.listen(options.port, options.host, callback);
+16 -7
View File
@@ -14,6 +14,16 @@ var RestQuery = require('./RestQuery');
var RestWrite = require('./RestWrite');
var triggers = require('./triggers');
function checkTriggers(className, config, types) {
return types.some((triggerType) => {
return triggers.getTrigger(className, triggers.Types[triggerType], config.applicationId);
});
}
function checkLiveQuery(className, config) {
return config.liveQueryController && config.liveQueryController.hasLiveQuery(className)
}
// Returns a promise for an object with optional keys 'results' and 'count'.
function find(config, auth, className, restWhere, restOptions, clientSDK) {
enforceRoleSecurity('find', className, auth);
@@ -49,10 +59,9 @@ function del(config, auth, className, objectId) {
var inflatedObject;
return Promise.resolve().then(() => {
if (triggers.getTrigger(className, triggers.Types.beforeDelete, config.applicationId) ||
triggers.getTrigger(className, triggers.Types.afterDelete, config.applicationId) ||
(config.liveQueryController && config.liveQueryController.hasLiveQuery(className)) ||
className == '_Session') {
const hasTriggers = checkTriggers(className, config, ['beforeDelete', 'afterDelete']);
const hasLiveQuery = checkLiveQuery(className, config);
if (hasTriggers || hasLiveQuery || className == '_Session') {
return find(config, Auth.master(config), className, {objectId: objectId})
.then((response) => {
if (response && response.results && response.results.length) {
@@ -108,9 +117,9 @@ function update(config, auth, className, objectId, restObject, clientSDK) {
enforceRoleSecurity('update', className, auth);
return Promise.resolve().then(() => {
if (triggers.getTrigger(className, triggers.Types.beforeSave, config.applicationId) ||
triggers.getTrigger(className, triggers.Types.afterSave, config.applicationId) ||
(config.liveQueryController && config.liveQueryController.hasLiveQuery(className))) {
const hasTriggers = checkTriggers(className, config, ['beforeSave', 'afterSave']);
const hasLiveQuery = checkLiveQuery(className, config);
if (hasTriggers || hasLiveQuery) {
return find(config, Auth.master(config), className, {objectId: objectId});
}
return Promise.resolve({});