mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a45d1848db | ||
|
|
6381e15e6d | ||
|
|
73195627c0 | ||
|
|
053bbefda9 | ||
|
|
032d5e55ca | ||
|
|
fe5947ce4c | ||
|
|
0e900cbefd | ||
|
|
41358d2226 | ||
|
|
73260897cd | ||
|
|
d3e6c0dea3 | ||
|
|
b4e27e1160 | ||
|
|
79b0c790f0 | ||
|
|
64f608ec9f | ||
|
|
67260b5fe0 | ||
|
|
411daf4d41 | ||
|
|
7a3c9cd33a | ||
|
|
193e5a4278 | ||
|
|
6ae0675010 | ||
|
|
a520ea0243 | ||
|
|
58bdeeee2d | ||
|
|
fc7f9f76f5 | ||
|
|
ef8d534679 | ||
|
|
1c9852e53f | ||
|
|
3f0de0c2ed | ||
|
|
eccdeccb59 | ||
|
|
f16f8423db | ||
|
|
bd60a5c1d1 | ||
|
|
40b9f953ca | ||
|
|
15eafd4dcc | ||
|
|
11cde12245 | ||
|
|
5861996cb0 | ||
|
|
ca70ad0141 |
@@ -1,50 +1,28 @@
|
||||
Please read the following instructions carefully.
|
||||
We use GitHub Issues for bugs.
|
||||
|
||||
Check out https://github.com/ParsePlatform/parse-server/issues/1271 for an ideal bug report.
|
||||
The closer your issue report is to that one, the more likely we are to be able to help, and the more likely we will be to fix the issue quickly!
|
||||
If you have a non-bug question, ask on Stack Overflow or Server Fault:
|
||||
- https://stackoverflow.com/questions/tagged/parse.com
|
||||
- https://serverfault.com/tags/parse
|
||||
|
||||
Many members of the community use Stack Overflow and Server Fault to ask questions.
|
||||
Read through the existing questions or ask your own!
|
||||
- Stack Overflow: http://stackoverflow.com/questions/tagged/parse.com
|
||||
- Server Fault: https://serverfault.com/tags/parse
|
||||
You may also search through existing issues before opening a new one: https://github.com/ParsePlatform/Parse-Server/issues?utf8=%E2%9C%93&q=is%3Aissue
|
||||
|
||||
For database migration help, please file a bug report at https://parse.com/help#report
|
||||
|
||||
Make sure these boxes are checked before submitting your issue -- thanks for reporting issues back to Parse Server!
|
||||
|
||||
- [ ] You've met the prerequisites: https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#prerequisites.
|
||||
|
||||
- [ ] You're running the latest version of Parse Server: https://github.com/ParsePlatform/parse-server/releases
|
||||
|
||||
- [ ] You've searched through existing issues: https://github.com/ParsePlatform/Parse-Server/issues?utf8=%E2%9C%93&q=is%3Aissue Chances are that your issue has been reported or resolved before.
|
||||
|
||||
- [ ] You have filled out every section below. Issues without sufficient information are more likely to be closed.
|
||||
|
||||
--
|
||||
--- Please use this template. If you don't use this template, your issue may be closed without comment. ---
|
||||
|
||||
### Issue Description
|
||||
|
||||
[DELETE EVERYTHING ABOVE THIS LINE BEFORE SUBMITTING YOUR ISSUE]
|
||||
|
||||
Describe your issue in as much detail as possible.
|
||||
|
||||
[FILL THIS OUT]
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
Please include a detailed list of steps that reproduce the issue. Include curl commands when applicable.
|
||||
|
||||
1. [FILL THIS OUT]
|
||||
2. [FILL THIS OUT]
|
||||
3. [FILL THIS OUT]
|
||||
|
||||
#### Expected Results
|
||||
|
||||
[FILL THIS OUT]
|
||||
What you expected to happen.
|
||||
|
||||
#### Actual Outcome
|
||||
|
||||
[FILL THIS OUT]
|
||||
What is happening instead.
|
||||
|
||||
### Environment Setup
|
||||
|
||||
@@ -62,6 +40,4 @@ Please include a detailed list of steps that reproduce the issue. Include curl c
|
||||
|
||||
### Logs/Trace
|
||||
|
||||
You can turn on additional logging by configuring VERBOSE=1 in your environment.
|
||||
|
||||
[FILL THIS OUT]
|
||||
Include all relevant logs. You can turn on additional logging by configuring VERBOSE=1 in your environment.
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+32
-1
@@ -1,5 +1,36 @@
|
||||
## Parse Server Changelog
|
||||
|
||||
### 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
|
||||
(#3497), thanks to [Arthur Cinader](https://github.com/acinader)
|
||||
* Fix LiveQuery unsafe user
|
||||
(#3525), thanks to [David Starke](https://github.com/dstarke)
|
||||
* Use `flushdb` instead of `flushall` in RedisCacheAdapter
|
||||
(#3523), thanks to [Jeremy Louie](https://github.com/JeremyPlease)
|
||||
* Fix saving GeoPoints and Files in `_GlobalConfig` (Make sure we don't treat
|
||||
dot notation keys as topLevel atoms)
|
||||
(#3531), thanks to [Florent Vilmart](https://github.com/flovilmart)
|
||||
|
||||
### 2.3.3
|
||||
[Full Changelog](https://github.com/ParsePlatform/parse-server/compare/2.3.2...2.3.3)
|
||||
|
||||
@@ -101,7 +132,7 @@ Postgres support requires v9.5
|
||||
* Better Postgres support, thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
* Logs the function name when failing (#2963), thanks to [Michael Helvey](https://github.com/michaelhelvey)
|
||||
* CLI: forces closing the connections with SIGINT/SIGTERM (#2964), thanks to [Kulshekhar Kabra](https://github.com/kulshekhar)
|
||||
* Reduce the number of calls to the _SCHEMA table (#2912), thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* Reduce the number of calls to the `_SCHEMA` table (#2912), thanks to [Steven Shipton](https://github.com/steven-supersolid)
|
||||
* LiveQuery: Support for Role ACL's, thanks to [Aaron Blondeau](https://github.com/aaron-blondeau-dose)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+2
-2
@@ -95,10 +95,10 @@ confirm 'Y' 'Do you want to continue? (Y/n): '
|
||||
check_node
|
||||
check_npm
|
||||
|
||||
printf "Setting up parse-server in %s" "${INSTALL_DIR}\n"
|
||||
printf "Setting up parse-server in %s\n" "${INSTALL_DIR}"
|
||||
|
||||
if [ -d "${INSTALL_DIR}" ]; then
|
||||
echo "{CHECK} ${INSTALL_DIR} exists"
|
||||
echo "${CHECK} ${INSTALL_DIR} exists"
|
||||
else
|
||||
mkdir -p "${INSTALL_DIR}"
|
||||
echo "${CHECK} Created ${INSTALL_DIR}"
|
||||
|
||||
+12
-11
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "2.3.3",
|
||||
"version": "2.3.6",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -19,7 +19,7 @@
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "1.16.0",
|
||||
"body-parser": "1.16.1",
|
||||
"commander": "2.9.0",
|
||||
"deepcopy": "0.6.3",
|
||||
"express": "4.14.1",
|
||||
@@ -27,35 +27,36 @@
|
||||
"lodash": "4.17.4",
|
||||
"lru-cache": "4.0.2",
|
||||
"mime": "1.3.4",
|
||||
"mongodb": "2.2.22",
|
||||
"mongodb": "2.2.24",
|
||||
"multer": "1.3.0",
|
||||
"parse": "1.9.2",
|
||||
"parse-server-fs-adapter": "1.0.1",
|
||||
"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.6",
|
||||
"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": {
|
||||
"babel-cli": "6.22.2",
|
||||
"babel-core": "6.22.1",
|
||||
|
||||
"babel-cli": "6.23.0",
|
||||
"babel-core": "6.23.1",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-plugin-syntax-flow": "6.13.0",
|
||||
"babel-plugin-transform-flow-strip-types": "6.22.0",
|
||||
"babel-preset-es2015": "6.22.0",
|
||||
"babel-preset-stage-0": "6.22.0",
|
||||
"babel-register": "6.22.0",
|
||||
"babel-register": "6.23.0",
|
||||
"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",
|
||||
@@ -68,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",
|
||||
@@ -85,6 +86,6 @@
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bcrypt": "1.0.2",
|
||||
"uws": "^0.12.0"
|
||||
"uws": "^0.13.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -79,6 +79,19 @@ describe('middlewares', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed when client key supplied but empty', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
clientKey: '',
|
||||
masterKey: 'masterKey',
|
||||
restAPIKey: 'restAPIKey'
|
||||
});
|
||||
fakeReq.headers['x-parse-client-key'] = '';
|
||||
middlewares.handleParseHeaders(fakeReq, fakeRes, () => {
|
||||
expect(fakeRes.status).not.toHaveBeenCalled();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed when no keys are configured and none supplied', (done) => {
|
||||
AppCache.put(fakeReq.body._ApplicationId, {
|
||||
masterKey: 'masterKey'
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
module.exports = function(options) {
|
||||
return {
|
||||
options: options,
|
||||
send: function() {},
|
||||
getValidPushTypes: function() {
|
||||
return Object.keys(options.options);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -56,6 +56,49 @@ describe('a GlobalConfig', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('can add and retrive files', (done) => {
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
body : { params: { file: { __type: 'File', name: 'name', url: 'http://url' } } },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(body.result).toEqual(true);
|
||||
Parse.Config.get().then((res) => {
|
||||
const file = res.get('file');
|
||||
expect(file.name()).toBe('name');
|
||||
expect(file.url()).toBe('http://url');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('can add and retrive Geopoints', (done) => {
|
||||
const geopoint = new Parse.GeoPoint(10,-20);
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
json : true,
|
||||
body : { params: { point: geopoint.toJSON() } },
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key' : 'test'
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(body.result).toEqual(true);
|
||||
Parse.Config.get().then((res) => {
|
||||
const point = res.get('point');
|
||||
expect(point.latitude).toBe(10);
|
||||
expect(point.longitude).toBe(-20);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('properly handles delete op', (done) => {
|
||||
request.put({
|
||||
url : 'http://localhost:8378/1/config',
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -4,13 +4,14 @@ describe('SessionTokenCache', function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
var Parse = require('parse/node');
|
||||
// Mock parse
|
||||
var mockUser = {
|
||||
become: jasmine.createSpy('become').and.returnValue(Parse.Promise.as({
|
||||
id: 'userId'
|
||||
}))
|
||||
}
|
||||
jasmine.mockLibrary('parse/node', 'User', mockUser);
|
||||
|
||||
spyOn(Parse, "Query").and.returnValue({
|
||||
first: jasmine.createSpy("first").and.returnValue(Parse.Promise.as(new Parse.Object("_Session", {
|
||||
user: new Parse.User({id:"userId"})
|
||||
}))),
|
||||
equalTo: function(){}
|
||||
})
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -46,7 +47,4 @@ describe('SessionTokenCache', function() {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
jasmine.restoreLibrary('parse/node', 'User');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,7 +71,7 @@ export class RedisCacheAdapter {
|
||||
debug('clear');
|
||||
this.p = this.p.then(() => {
|
||||
return new Promise((resolve) => {
|
||||
this.client.flushall(function() {
|
||||
this.client.flushdb(function() {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -89,6 +89,9 @@ const transformKeyValueForUpdate = (className, restKey, restValue, parseFormatSc
|
||||
if (timeField && (typeof value === 'string')) {
|
||||
value = new Date(value);
|
||||
}
|
||||
if (restKey.indexOf('.') > 0) {
|
||||
return {key, value: restValue}
|
||||
}
|
||||
return {key, value};
|
||||
}
|
||||
|
||||
@@ -98,7 +101,7 @@ const transformKeyValueForUpdate = (className, restKey, restValue, parseFormatSc
|
||||
return {key, value};
|
||||
}
|
||||
|
||||
// Handle update operators
|
||||
// Handle update operators
|
||||
if (typeof restValue === 'object' && '__op' in restValue) {
|
||||
return {key, value: transformUpdateOperator(restValue, false)};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createClient } from './PostgresClient';
|
||||
import Parse from 'parse/node';
|
||||
import _ from 'lodash';
|
||||
import sql from './sql';
|
||||
|
||||
const PostgresRelationDoesNotExistError = '42P01';
|
||||
const PostgresDuplicateRelationError = '42P07';
|
||||
@@ -1171,22 +1172,22 @@ export class PostgresStorageAdapter {
|
||||
});
|
||||
/* eslint-disable no-console */
|
||||
promises = promises.concat([
|
||||
this._client.any(json_object_set_key).catch((err) => {
|
||||
this._client.none(sql.misc.jsonObjectSetKeys).catch((err) => {
|
||||
console.error(err);
|
||||
}),
|
||||
this._client.any(array_add).catch((err) => {
|
||||
this._client.none(sql.array.add).catch((err) => {
|
||||
console.error(err);
|
||||
}),
|
||||
this._client.any(array_add_unique).catch((err) => {
|
||||
this._client.none(sql.array.addUnique).catch((err) => {
|
||||
console.error(err);
|
||||
}),
|
||||
this._client.any(array_remove).catch((err) => {
|
||||
this._client.none(sql.array.remove).catch((err) => {
|
||||
console.error(err);
|
||||
}),
|
||||
this._client.any(array_contains_all).catch((err) => {
|
||||
this._client.none(sql.array.containsAll).catch((err) => {
|
||||
console.error(err);
|
||||
}),
|
||||
this._client.any(array_contains).catch((err) => {
|
||||
this._client.none(sql.array.contains).catch((err) => {
|
||||
console.error(err);
|
||||
})
|
||||
]);
|
||||
@@ -1270,84 +1271,5 @@ function literalizeRegexPart(s) {
|
||||
);
|
||||
}
|
||||
|
||||
// Function to set a key on a nested JSON document
|
||||
const json_object_set_key = 'CREATE OR REPLACE FUNCTION "json_object_set_key"(\
|
||||
"json" jsonb,\
|
||||
"key_to_set" TEXT,\
|
||||
"value_to_set" anyelement\
|
||||
)\
|
||||
RETURNS jsonb \
|
||||
LANGUAGE sql \
|
||||
IMMUTABLE \
|
||||
STRICT \
|
||||
AS $function$\
|
||||
SELECT concat(\'{\', string_agg(to_json("key") || \':\' || "value", \',\'), \'}\')::jsonb\
|
||||
FROM (SELECT *\
|
||||
FROM jsonb_each("json")\
|
||||
WHERE "key" <> "key_to_set"\
|
||||
UNION ALL\
|
||||
SELECT "key_to_set", to_json("value_to_set")::jsonb) AS "fields"\
|
||||
$function$;'
|
||||
|
||||
const array_add = `CREATE OR REPLACE FUNCTION "array_add"(
|
||||
"array" jsonb,
|
||||
"values" jsonb
|
||||
)
|
||||
RETURNS jsonb
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
STRICT
|
||||
AS $function$
|
||||
SELECT array_to_json(ARRAY(SELECT unnest(ARRAY(SELECT DISTINCT jsonb_array_elements("array")) || ARRAY(SELECT jsonb_array_elements("values")))))::jsonb;
|
||||
$function$;`;
|
||||
|
||||
const array_add_unique = `CREATE OR REPLACE FUNCTION "array_add_unique"(
|
||||
"array" jsonb,
|
||||
"values" jsonb
|
||||
)
|
||||
RETURNS jsonb
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
STRICT
|
||||
AS $function$
|
||||
SELECT array_to_json(ARRAY(SELECT DISTINCT unnest(ARRAY(SELECT DISTINCT jsonb_array_elements("array")) || ARRAY(SELECT DISTINCT jsonb_array_elements("values")))))::jsonb;
|
||||
$function$;`;
|
||||
|
||||
const array_remove = `CREATE OR REPLACE FUNCTION "array_remove"(
|
||||
"array" jsonb,
|
||||
"values" jsonb
|
||||
)
|
||||
RETURNS jsonb
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
STRICT
|
||||
AS $function$
|
||||
SELECT array_to_json(ARRAY(SELECT * FROM jsonb_array_elements("array") as elt WHERE elt NOT IN (SELECT * FROM (SELECT jsonb_array_elements("values")) AS sub)))::jsonb;
|
||||
$function$;`;
|
||||
|
||||
const array_contains_all = `CREATE OR REPLACE FUNCTION "array_contains_all"(
|
||||
"array" jsonb,
|
||||
"values" jsonb
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
STRICT
|
||||
AS $function$
|
||||
SELECT RES.CNT = jsonb_array_length("values") FROM (SELECT COUNT(*) as CNT FROM jsonb_array_elements("array") as elt WHERE elt IN (SELECT jsonb_array_elements("values"))) as RES ;
|
||||
$function$;`;
|
||||
|
||||
const array_contains = `CREATE OR REPLACE FUNCTION "array_contains"(
|
||||
"array" jsonb,
|
||||
"values" jsonb
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
STRICT
|
||||
AS $function$
|
||||
SELECT RES.CNT >= 1 FROM (SELECT COUNT(*) as CNT FROM jsonb_array_elements("array") as elt WHERE elt IN (SELECT jsonb_array_elements("values"))) as RES ;
|
||||
$function$;`;
|
||||
|
||||
export default PostgresStorageAdapter;
|
||||
module.exports = PostgresStorageAdapter; // Required for tests
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
CREATE OR REPLACE FUNCTION array_add_unique(
|
||||
"array" jsonb,
|
||||
"values" jsonb
|
||||
)
|
||||
RETURNS jsonb
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
STRICT
|
||||
AS $function$
|
||||
SELECT array_to_json(ARRAY(SELECT DISTINCT unnest(ARRAY(SELECT DISTINCT jsonb_array_elements("array")) || ARRAY(SELECT DISTINCT jsonb_array_elements("values")))))::jsonb;
|
||||
$function$;
|
||||
@@ -0,0 +1,11 @@
|
||||
CREATE OR REPLACE FUNCTION array_add(
|
||||
"array" jsonb,
|
||||
"values" jsonb
|
||||
)
|
||||
RETURNS jsonb
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
STRICT
|
||||
AS $function$
|
||||
SELECT array_to_json(ARRAY(SELECT unnest(ARRAY(SELECT DISTINCT jsonb_array_elements("array")) || ARRAY(SELECT jsonb_array_elements("values")))))::jsonb;
|
||||
$function$;
|
||||
@@ -0,0 +1,11 @@
|
||||
CREATE OR REPLACE FUNCTION array_contains_all(
|
||||
"array" jsonb,
|
||||
"values" jsonb
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
STRICT
|
||||
AS $function$
|
||||
SELECT RES.CNT = jsonb_array_length("values") FROM (SELECT COUNT(*) as CNT FROM jsonb_array_elements("array") as elt WHERE elt IN (SELECT jsonb_array_elements("values"))) as RES;
|
||||
$function$;
|
||||
@@ -0,0 +1,11 @@
|
||||
CREATE OR REPLACE FUNCTION array_contains(
|
||||
"array" jsonb,
|
||||
"values" jsonb
|
||||
)
|
||||
RETURNS boolean
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
STRICT
|
||||
AS $function$
|
||||
SELECT RES.CNT >= 1 FROM (SELECT COUNT(*) as CNT FROM jsonb_array_elements("array") as elt WHERE elt IN (SELECT jsonb_array_elements("values"))) as RES;
|
||||
$function$;
|
||||
@@ -0,0 +1,11 @@
|
||||
CREATE OR REPLACE FUNCTION array_remove(
|
||||
"array" jsonb,
|
||||
"values" jsonb
|
||||
)
|
||||
RETURNS jsonb
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
STRICT
|
||||
AS $function$
|
||||
SELECT array_to_json(ARRAY(SELECT * FROM jsonb_array_elements("array") as elt WHERE elt NOT IN (SELECT * FROM (SELECT jsonb_array_elements("values")) AS sub)))::jsonb;
|
||||
$function$;
|
||||
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
var QueryFile = require('pg-promise').QueryFile;
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
array: {
|
||||
add: sql('array/add.sql'),
|
||||
addUnique: sql('array/add-unique.sql'),
|
||||
contains: sql('array/contains.sql'),
|
||||
containsAll: sql('array/contains-all.sql'),
|
||||
remove: sql('array/remove.sql')
|
||||
},
|
||||
misc: {
|
||||
jsonObjectSetKeys: sql('misc/json-object-set-keys.sql')
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Helper for linking to external query files;
|
||||
function sql(file) {
|
||||
|
||||
var fullPath = path.join(__dirname, file); // generating full path;
|
||||
|
||||
var qf = new QueryFile(fullPath, {minify: true});
|
||||
|
||||
if (qf.error) {
|
||||
throw qf.error;
|
||||
}
|
||||
|
||||
return qf;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
-- Function to set a key on a nested JSON document
|
||||
|
||||
CREATE OR REPLACE FUNCTION json_object_set_key(
|
||||
"json" jsonb,
|
||||
key_to_set TEXT,
|
||||
value_to_set anyelement
|
||||
)
|
||||
RETURNS jsonb
|
||||
LANGUAGE sql
|
||||
IMMUTABLE
|
||||
STRICT
|
||||
AS $function$
|
||||
SELECT concat('{', string_agg(to_json("key") || ':' || "value", ','), '}')::jsonb
|
||||
FROM (SELECT *
|
||||
FROM jsonb_each("json")
|
||||
WHERE key <> key_to_set
|
||||
UNION ALL
|
||||
SELECT key_to_set, to_json("value_to_set")::jsonb) AS fields
|
||||
$function$;
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -37,7 +37,6 @@ class ParseLiveQueryServer {
|
||||
|
||||
// Initialize Parse
|
||||
Parse.Object.disableSingleInstance();
|
||||
Parse.User.enableUnsafeCurrentUser();
|
||||
|
||||
const serverURL = config.serverURL || Parse.serverURL;
|
||||
Parse.serverURL = serverURL;
|
||||
@@ -363,7 +362,7 @@ class ParseLiveQueryServer {
|
||||
// Then get the user's roles
|
||||
var rolesQuery = new Parse.Query(Parse.Role);
|
||||
rolesQuery.equalTo("users", user);
|
||||
return rolesQuery.find();
|
||||
return rolesQuery.find({useMasterKey:true});
|
||||
}).
|
||||
then((roles) => {
|
||||
|
||||
|
||||
@@ -2,6 +2,17 @@ import Parse from 'parse/node';
|
||||
import LRU from 'lru-cache';
|
||||
import logger from '../logger';
|
||||
|
||||
function userForSessionToken(sessionToken){
|
||||
var q = new Parse.Query("_Session");
|
||||
q.equalTo("sessionToken", sessionToken);
|
||||
return q.first({useMasterKey:true}).then(function(session){
|
||||
if(!session){
|
||||
return Parse.Promise.error("No session found for session token");
|
||||
}
|
||||
return session.get("user");
|
||||
});
|
||||
}
|
||||
|
||||
class SessionTokenCache {
|
||||
cache: Object;
|
||||
|
||||
@@ -21,7 +32,7 @@ class SessionTokenCache {
|
||||
logger.verbose('Fetch userId %s of sessionToken %s from Cache', userId, sessionToken);
|
||||
return Parse.Promise.as(userId);
|
||||
}
|
||||
return Parse.User.become(sessionToken).then((user) => {
|
||||
return userForSessionToken(sessionToken).then((user) => {
|
||||
logger.verbose('Fetch userId %s of sessionToken %s from Parse', user.id, sessionToken);
|
||||
const userId = user.id;
|
||||
this.cache.set(sessionToken, userId);
|
||||
|
||||
+1
-1
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
+2
-2
@@ -122,10 +122,10 @@ export function handleParseHeaders(req, res, next) {
|
||||
// to preserve original behavior.
|
||||
const keys = ["clientKey", "javascriptKey", "dotNetKey", "restAPIKey"];
|
||||
const oneKeyConfigured = keys.some(function(key) {
|
||||
return req.config[key];
|
||||
return req.config[key] !== undefined;
|
||||
});
|
||||
const oneKeyMatches = keys.some(function(key){
|
||||
return req.config[key] && info[key] == req.config[key];
|
||||
return req.config[key] !== undefined && info[key] === req.config[key];
|
||||
});
|
||||
|
||||
if (oneKeyConfigured && !oneKeyMatches) {
|
||||
|
||||
+16
-7
@@ -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({});
|
||||
|
||||
Reference in New Issue
Block a user