mirror of
https://github.com/parse-community/parse-server
synced 2026-08-09 13:03:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
213bfea62d | ||
|
|
a17e54c397 | ||
|
|
5773143973 | ||
|
|
406f237119 | ||
|
|
bc384e97e1 | ||
|
|
18906f17b4 | ||
|
|
442c723350 | ||
|
|
9053b941ee | ||
|
|
7e52c18893 | ||
|
|
8c92b80b8f | ||
|
|
27dd2ebf5d | ||
|
|
c1ef0440b0 | ||
|
|
f4889a3393 | ||
|
|
19da14f3df | ||
|
|
91de7504fe | ||
|
|
ec6a39718d | ||
|
|
0e3636d880 | ||
|
|
89df778ac8 | ||
|
|
acc23d0828 | ||
|
|
3a28352c96 | ||
|
|
f99b5588ab | ||
|
|
51664c8f33 | ||
|
|
8c12f80d28 | ||
|
|
c346e15a18 | ||
|
|
4fd67a51fb | ||
|
|
fffc83c0d9 | ||
|
|
04e8e3e233 | ||
|
|
02af61dc9a | ||
|
|
57156cf52b | ||
|
|
01af755d18 | ||
|
|
3f8a1aae02 | ||
|
|
edfa4092c0 | ||
|
|
431d864ac3 | ||
|
|
647987534f | ||
|
|
ca7d8580e3 | ||
|
|
9c528c6fe8 | ||
|
|
2b3bf7c2b7 | ||
|
|
6540ad1483 | ||
|
|
b5625bc2e5 | ||
|
|
09279d1987 | ||
|
|
dbf64a6d83 | ||
|
|
eeb3331167 | ||
|
|
8a6903aa8d | ||
|
|
434ac91129 | ||
|
|
f0ebb7bae6 | ||
|
|
781e81d36b | ||
|
|
73bca3b64c | ||
|
|
6311c95785 | ||
|
|
efa7366250 | ||
|
|
6b6e684d55 | ||
|
|
5d99075663 | ||
|
|
c5636e1b77 | ||
|
|
ab1858616b | ||
|
|
632c8054da | ||
|
|
7afc08abe4 | ||
|
|
33c9730be2 | ||
|
|
97d3deb73b | ||
|
|
a0e7739526 | ||
|
|
7064f1d3ed | ||
|
|
4774e90937 | ||
|
|
6055f2a552 | ||
|
|
3dccd61222 |
@@ -1,3 +1,5 @@
|
||||
Check out [this issue](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!
|
||||
|
||||
For implementation related questions or technical support, please refer to the [Stack Overflow](http://stackoverflow.com/questions/tagged/parse.com) and [Server Fault](https://serverfault.com/tags/parse) communities.
|
||||
|
||||
Make sure these boxes are checked before submitting your issue -- thanks for reporting issues back to Parse Server!
|
||||
|
||||
+16
-10
@@ -1,16 +1,22 @@
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
language: node_js
|
||||
node_js:
|
||||
- "4.3"
|
||||
- '4.3'
|
||||
env:
|
||||
global:
|
||||
- COVERAGE_OPTION='./node_modules/babel-istanbul/lib/cli.js cover -x **/spec/**'
|
||||
global:
|
||||
- COVERAGE_OPTION='./node_modules/babel-istanbul/lib/cli.js cover -x **/spec/**'
|
||||
matrix:
|
||||
- MONGODB_VERSION=2.6.11
|
||||
- MONGODB_VERSION=3.0.8
|
||||
- MONGODB_VERSION=2.6.11
|
||||
- MONGODB_VERSION=3.0.8
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.mongodb/versions/downloads
|
||||
after_success: ./node_modules/.bin/codecov
|
||||
- "$HOME/.mongodb/versions/downloads"
|
||||
after_success: "./node_modules/.bin/codecov"
|
||||
deploy:
|
||||
provider: npm
|
||||
email:
|
||||
secure: T3LwSJFcu632SDfCoavcKL39NktWLEOOFzonAjeHAglmMyDp9hvU8xHwQ4JJy2CRA03c+ezAe2gH3Va+jfxvh1JtFUan+p88vaCHHKuNwPSp4WZBPt1ZTDmG+ACG6j+ZWHK+eP7hLvtlCi/M9/v4/bVojpm7v01LLiM+MRhnE9E7eLemQC4zb6RVtf2oGQ3mX51kMeya218kEm5bsQPpRJElI9jwletFa8qYK5AFgaCHpll059RRHQTTE9MTNcN58P/Kf6Qb3KGpKNoaHTXoOD4U6LcXLWvdHONVB6AzGefxg2b/dvtaO4nd5lDooMBR3u9kWpopXkyAuf+fO/8je9JTxO6CDUtDjHRcR2mCEXWX6rculIAbNXOv1ccRYCTQ8aFtYnFlMSb1+QIAdyT6HHmeT5ktk3+XJRuEv0PJJvqHTo+j7tPngjiv5sPNutgGFlOFO90omTbzEkIT/D/LsgXbWm2QKYWejbLiuSINg+FiFiZN90doCf4aCpm2y1Q/9GzvK+eXcpqzPCGxOykE7EuPZK3+S/ZU2oaWL2uSpbKPtC0qxebrneR307aVEa7C85HCkTMMBzE4tjUr8h5HhLqtWvqmDcnBo3PfQhf9cEO6jQLd3qhEVJmcwKz/yOCfgPXlCbRqiKk7j94perpNCIalXtMI9ySYjJNjHsB4600=
|
||||
api_key:
|
||||
secure: WLjhHVAdUkDy6UdNWveTpZqphw9olN0alCpyPpU0cJjlN/hk7YxPP+YHOSVnMZaRZoHM0LL11jPKLf970nymBfvJHDWxKk71c/5xyTX8pBtSxmGmGy23a5g2VrHVMd2JxsI7NEAH500tlFz+01t4E3Steo0NpAkHR3Q51kj01gQy8IumsksfhRc9uvjZ7zjm99Yk4L+cxyei79v4esfpx7Bgm0HTfAAAG/19F+r0hvvFiV517SREDU/YtcX/rIjVepEy1uiLKEohnmYtVIRSA3Hjh6VlHVhdj2WDd/dYrxi/Ioysq2zOM9pZVLamO9asB1e6JrTEipWE9jSZXxsbnfFzuNLxcOjKv1wd3OzQKF/7pGKGiWRTv5Xm19D/FrXoE/ULs6bHcC/Ke8Gs2RxPlOCdvAFehCkyf5P4HOPCQdR7o8Yiuvt+5JWKBflElNbSd4nWgwUOo3Yv8vC4Vj53fwmM+Uqfu3IgYFWktYxCm4RWIKMfB/gtmjcF6QYFfjvEMwAvRfBV81kuynCsnubXWzQeBE/b3JOhBfpGciKCjKfy+tS6bZfFjCtQV98hMMiCPre8Y7PahHDYc65wU9Ake+ZE+dDaSbeV3DZ5JeifLJHzKW2J2dWeRANkOaiSwO9VBC9/rht5ulK5qQ1pB2+sKDToAaiKy6RMlB/HgIoYqsU=
|
||||
on:
|
||||
tags: true
|
||||
repo: ParsePlatform/parse-server
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
## Parse Server Changelog
|
||||
|
||||
### 2.2.6 (4/5/2016)
|
||||
|
||||
* Important Fix: Disables find on installation from clients [\#1374](https://github.com/ParsePlatform/parse-server/pull/1374) ([flovilmart](https://github.com/flovilmart))
|
||||
* Adds missing options to the CLI [\#1368](https://github.com/ParsePlatform/parse-server/pull/1368) ([flovilmart](https://github.com/flovilmart))
|
||||
* Removes only master on travis [\#1367](https://github.com/ParsePlatform/parse-server/pull/1367) ([flovilmart](https://github.com/flovilmart))
|
||||
* Auth.\_loadRoles should not query the same role twice. [\#1366](https://github.com/ParsePlatform/parse-server/pull/1366) ([blacha](https://github.com/blacha))
|
||||
|
||||
### 2.2.5 (4/4/2016)
|
||||
|
||||
* Improves config loading and tests [\#1363](https://github.com/ParsePlatform/parse-server/pull/1363) ([flovilmart](https://github.com/flovilmart))
|
||||
* Adds travis configuration to deploy NPM on new version tags [\#1361](https://github.com/ParsePlatform/parse-server/pull/1361) ([gfosco](https://github.com/gfosco))
|
||||
* Inject the default schemas properties when loading it [\#1357](https://github.com/ParsePlatform/parse-server/pull/1357) ([flovilmart](https://github.com/flovilmart))
|
||||
* Adds console transport when testing with VERBOSE=1 [\#1351](https://github.com/ParsePlatform/parse-server/pull/1351) ([flovilmart](https://github.com/flovilmart))
|
||||
* Make notEqual work on relations [\#1350](https://github.com/ParsePlatform/parse-server/pull/1350) ([flovilmart](https://github.com/flovilmart))
|
||||
* Accept only bool for $exists in LiveQuery [\#1315](https://github.com/ParsePlatform/parse-server/pull/1315) ([drew-gross](https://github.com/drew-gross))
|
||||
* Adds more options when using CLI/config [\#1305](https://github.com/ParsePlatform/parse-server/pull/1305) ([flovilmart](https://github.com/flovilmart))
|
||||
* Update error message [\#1297](https://github.com/ParsePlatform/parse-server/pull/1297) ([drew-gross](https://github.com/drew-gross))
|
||||
* Properly let masterKey add fields [\#1291](https://github.com/ParsePlatform/parse-server/pull/1291) ([flovilmart](https://github.com/flovilmart))
|
||||
* Point to \#1271 as how to write a good issue report [\#1290](https://github.com/ParsePlatform/parse-server/pull/1290) ([drew-gross](https://github.com/drew-gross))
|
||||
* Adds ability to override mount with publicServerURL for production uses [\#1287](https://github.com/ParsePlatform/parse-server/pull/1287) ([flovilmart](https://github.com/flovilmart))
|
||||
* Single object queries to use include and keys [\#1280](https://github.com/ParsePlatform/parse-server/pull/1280) ([jeremyjackson89](https://github.com/jeremyjackson89))
|
||||
* Improves report for Push error in logs and \_PushStatus [\#1269](https://github.com/ParsePlatform/parse-server/pull/1269) ([flovilmart](https://github.com/flovilmart))
|
||||
* Removes all stdout/err logs while testing [\#1268](https://github.com/ParsePlatform/parse-server/pull/1268) ([flovilmart](https://github.com/flovilmart))
|
||||
* Matching queries with doesNotExist constraint [\#1250](https://github.com/ParsePlatform/parse-server/pull/1250) ([andrecardoso](https://github.com/andrecardoso))
|
||||
* Added session length option for session tokens to server configuration [\#997](https://github.com/ParsePlatform/parse-server/pull/997) ([Kenishi](https://github.com/Kenishi))
|
||||
* Regression test for \#1259 [\#1286](https://github.com/ParsePlatform/parse-server/pull/1286) ([drew-gross](https://github.com/drew-gross))
|
||||
* Regression test for \#871 [\#1283](https://github.com/ParsePlatform/parse-server/pull/1283) ([drew-gross](https://github.com/drew-gross))
|
||||
* Add a test to repro \#701 [\#1281](https://github.com/ParsePlatform/parse-server/pull/1281) ([drew-gross](https://github.com/drew-gross))
|
||||
* Fix for \#1334: using relative cloud code files broken [\#1353](https://github.com/ParsePlatform/parse-server/pull/1353) ([airdrummingfool](https://github.com/airdrummingfool))
|
||||
* Fix Issue/1288 [\#1346](https://github.com/ParsePlatform/parse-server/pull/1346) ([flovilmart](https://github.com/flovilmart))
|
||||
* Fixes \#1271 [\#1295](https://github.com/ParsePlatform/parse-server/pull/1295) ([drew-gross](https://github.com/drew-gross))
|
||||
* Fixes issue \#1302 [\#1314](https://github.com/ParsePlatform/parse-server/pull/1314) ([flovilmart](https://github.com/flovilmart))
|
||||
* Fixes bug related to include in queries [\#1312](https://github.com/ParsePlatform/parse-server/pull/1312) ([flovilmart](https://github.com/flovilmart))
|
||||
|
||||
|
||||
### 2.2.4 (3/29/2016)
|
||||
|
||||
* Hotfix: fixed imports issue for S3Adapter, GCSAdapter, FileSystemAdapter [\#1263](https://github.com/ParsePlatform/parse-server/pull/1263) ([drew-gross](https://github.com/drew-gross)
|
||||
|
||||
@@ -104,7 +104,7 @@ Once you have a better understanding of how the project works, please refer to t
|
||||
|
||||
We have provided a basic [Node.js application](https://github.com/ParsePlatform/parse-server-example) that uses the Parse Server module on Express and can be easily deployed to various infrastructure providers:
|
||||
|
||||
* [Heroku and mLab](https://github.com/ParsePlatform/parse-server/wiki/Deploying-Parse-Server#deploying-to-heroku-and-mLab)
|
||||
* [Heroku and mLab](https://devcenter.heroku.com/articles/deploying-a-parse-server-to-heroku)
|
||||
* [AWS and Elastic Beanstalk](http://mobile.awsblog.com/post/TxCD57GZLM2JR/How-to-set-up-Parse-Server-on-AWS-using-AWS-Elastic-Beanstalk)
|
||||
* [Digital Ocean](https://www.digitalocean.com/community/tutorials/how-to-run-parse-server-on-ubuntu-14-04)
|
||||
* [NodeChef](https://nodechef.com/blog/post/6/migrate-from-parse-to-nodechef%E2%80%99s-managed-parse-server)
|
||||
@@ -185,7 +185,7 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
|
||||
* `filesAdapter` - The default behavior (GridStore) can be changed by creating an adapter class (see [`FilesAdapter.js`](https://github.com/ParsePlatform/parse-server/blob/master/src/Adapters/Files/FilesAdapter.js)).
|
||||
* `maxUploadSize` - Max file size for uploads. Defaults to 20 MB.
|
||||
* `loggerAdapter` - The default behavior/transport (File) can be changed by creating an adapter class (see [`LoggerAdapter.js`](https://github.com/ParsePlatform/parse-server/blob/master/src/Adapters/Logger/LoggerAdapter.js)).
|
||||
* `databaseAdapter` - The backing store can be changed by creating an adapter class (see `DatabaseAdapter.js`). Defaults to `MongoStorageAdapter`.
|
||||
* `sessionLength` - The length of time in seconds that a session should be valid for. Defaults to 31536000 seconds (1 year).
|
||||
|
||||
##### Email verification and password reset
|
||||
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parse-server",
|
||||
"version": "2.2.4",
|
||||
"version": "2.2.6",
|
||||
"description": "An express module providing a Parse-compatible API server",
|
||||
"main": "lib/index.js",
|
||||
"repository": {
|
||||
@@ -26,6 +26,7 @@
|
||||
"commander": "^2.9.0",
|
||||
"deepcopy": "^0.6.1",
|
||||
"express": "^4.13.4",
|
||||
"intersect": "^1.0.1",
|
||||
"lru-cache": "^4.0.0",
|
||||
"mailgun-js": "^0.7.7",
|
||||
"mime": "^1.3.4",
|
||||
|
||||
+60
-8
@@ -1,3 +1,4 @@
|
||||
'use strict';
|
||||
var commander = require("../src/cli/utils/commander").default;
|
||||
|
||||
var definitions = {
|
||||
@@ -11,7 +12,7 @@ var definitions = {
|
||||
action: function(value) {
|
||||
var value = parseInt(value);
|
||||
if (!Number.isInteger(value)) {
|
||||
throw "port is invalid";
|
||||
throw "arg2 is invalid";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -23,7 +24,7 @@ var definitions = {
|
||||
}
|
||||
|
||||
describe("commander additions", () => {
|
||||
|
||||
|
||||
afterEach((done) => {
|
||||
commander.options = [];
|
||||
delete commander.arg0;
|
||||
@@ -33,7 +34,7 @@ describe("commander additions", () => {
|
||||
delete commander.arg4;
|
||||
done();
|
||||
})
|
||||
|
||||
|
||||
it("should load properly definitions from args", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value", "--arg1", "arg1Value", "--arg2", "2", "--arg3", "some"]);
|
||||
@@ -44,7 +45,7 @@ describe("commander additions", () => {
|
||||
expect(commander.arg4).toEqual("arg4Value");
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it("should load properly definitions from env", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
commander.parse([], {
|
||||
@@ -58,7 +59,7 @@ describe("commander additions", () => {
|
||||
expect(commander.arg4).toEqual("arg4Value");
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it("should load properly use args over env", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value", "--arg4", "anotherArg4"], {
|
||||
@@ -72,7 +73,7 @@ describe("commander additions", () => {
|
||||
expect(commander.arg4).toEqual("anotherArg4");
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
it("should fail in action as port is invalid", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
expect(()=> {
|
||||
@@ -81,7 +82,58 @@ describe("commander additions", () => {
|
||||
"PROGRAM_ARG_1": "arg1ENVValue",
|
||||
"PROGRAM_ARG_2": "hello",
|
||||
});
|
||||
}).toThrow("port is invalid");
|
||||
}).toThrow("arg2 is invalid");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not override config.json", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value", "./spec/configs/CLIConfig.json"], {
|
||||
"PROGRAM_ARG_0": "arg0ENVValue",
|
||||
"PROGRAM_ARG_1": "arg1ENVValue",
|
||||
});
|
||||
let options = commander.getOptions();
|
||||
expect(options.arg2).toBe(8888);
|
||||
expect(options.arg3).toBe("hello"); //config value
|
||||
expect(options.arg4).toBe('/1');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail with invalid values in JSON", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
expect(() => {
|
||||
commander.parse(["node","./CLI.spec.js","--arg0", "arg0Value", "./spec/configs/CLIConfigFail.json"], {
|
||||
"PROGRAM_ARG_0": "arg0ENVValue",
|
||||
"PROGRAM_ARG_1": "arg1ENVValue",
|
||||
});
|
||||
}).toThrow("arg2 is invalid")
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail when too many apps are set", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
expect(() => {
|
||||
commander.parse(["node","./CLI.spec.js","./spec/configs/CLIConfigFailTooManyApps.json"]);
|
||||
}).toThrow("Multiple apps are not supported")
|
||||
done();
|
||||
});
|
||||
|
||||
it("should load config from apps", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
commander.parse(["node", "./CLI.spec.js", "./spec/configs/CLIConfigApps.json"]);
|
||||
let options = commander.getOptions();
|
||||
expect(options.arg1).toBe("my_app");
|
||||
expect(options.arg2).toBe(8888);
|
||||
expect(options.arg3).toBe("hello"); //config value
|
||||
expect(options.arg4).toBe('/1');
|
||||
done();
|
||||
});
|
||||
|
||||
it("should fail when passing an invalid arguement", (done) => {
|
||||
commander.loadDefinitions(definitions);
|
||||
expect(() => {
|
||||
commander.parse(["node", "./CLI.spec.js", "./spec/configs/CLIConfigUnknownArg.json"]);
|
||||
}).toThrow('error: unknown option myArg')
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
// This is a port of the test suite:
|
||||
// hungry/js/test/parse_acl_test.js
|
||||
var rest = require('../src/rest');
|
||||
var Config = require('../src/Config');
|
||||
var config = new Config('test');
|
||||
var auth = require('../src/Auth');
|
||||
|
||||
describe('Parse.ACL', () => {
|
||||
it("acl must be valid", (done) => {
|
||||
@@ -1158,4 +1162,34 @@ describe('Parse.ACL', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('regression test #701', done => {
|
||||
var anonUser = {
|
||||
authData: {
|
||||
anonymous: {
|
||||
id: '00000000-0000-0000-0000-000000000001'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Parse.Cloud.afterSave(Parse.User, req => {
|
||||
if (!req.object.existed()) {
|
||||
var user = req.object;
|
||||
var acl = new Parse.ACL(user);
|
||||
user.setACL(acl);
|
||||
user.save(null, {useMasterKey: true}).then(user => {
|
||||
new Parse.Query('_User').get(user.objectId).then(user => {
|
||||
fail('should not have fetched user without public read enabled');
|
||||
done();
|
||||
}, error => {
|
||||
expect(error.code).toEqual(Parse.Error.OBJECT_NOT_FOUND);
|
||||
Parse.Cloud._removeHook('Triggers', 'afterSave', Parse.User.className);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
rest.create(config, auth.nobody(config), '_User', anonUser)
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
@@ -618,6 +618,37 @@ describe('miscellaneous', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('pointer reassign is working properly (#1288)', (done) => {
|
||||
Parse.Cloud.beforeSave('GameScore', (req, res) => {
|
||||
|
||||
var obj = req.object;
|
||||
if (obj.get('point')) {
|
||||
return res.success();
|
||||
}
|
||||
var TestObject1 = Parse.Object.extend('TestObject1');
|
||||
var newObj = new TestObject1({'key1': 1});
|
||||
|
||||
return newObj.save().then((newObj) => {
|
||||
obj.set('point' , newObj);
|
||||
res.success();
|
||||
});
|
||||
});
|
||||
var pointId;
|
||||
var obj = new Parse.Object('GameScore');
|
||||
obj.set('foo', 'bar');
|
||||
obj.save().then(() => {
|
||||
expect(obj.get('point')).not.toBeUndefined();
|
||||
pointId = obj.get('point').id;
|
||||
expect(pointId).not.toBeUndefined();
|
||||
obj.set('foo', 'baz');
|
||||
return obj.save();
|
||||
}).then((obj) => {
|
||||
expect(obj.get('point').id).toEqual(pointId);
|
||||
Parse.Cloud._removeHook("Triggers", "beforeSave", "GameScore");
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('test afterSave get full object on create and update', function(done) {
|
||||
var triggerTime = 0;
|
||||
// Register a mock beforeSave hook
|
||||
|
||||
@@ -119,6 +119,50 @@ describe('Installations', () => {
|
||||
}).catch((error) => { console.log(error); });
|
||||
});
|
||||
|
||||
it('should properly fail queying installations', (done) => {
|
||||
var installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
var device = 'android';
|
||||
var input = {
|
||||
'installationId': installId,
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
let query = new Parse.Query(Parse.Installation);
|
||||
return query.find()
|
||||
}).then((results) => {
|
||||
fail('Should not succeed!');
|
||||
done();
|
||||
}).catch((error) => {
|
||||
expect(error.code).toBe(119);
|
||||
expect(error.message).toBe('Clients aren\'t allowed to perform the find operation on the installation collection.')
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should properly queying installations with masterKey', (done) => {
|
||||
var installId = '12345678-abcd-abcd-abcd-123456789abc';
|
||||
var device = 'android';
|
||||
var input = {
|
||||
'installationId': installId,
|
||||
'deviceType': device
|
||||
};
|
||||
rest.create(config, auth.nobody(config), '_Installation', input)
|
||||
.then(() => {
|
||||
let query = new Parse.Query(Parse.Installation);
|
||||
return query.find({useMasterKey: true});
|
||||
}).then((results) => {
|
||||
expect(results.length).toEqual(1);
|
||||
var obj = results[0].toJSON();
|
||||
expect(obj.installationId).toEqual(installId);
|
||||
expect(obj.deviceType).toEqual(device);
|
||||
done();
|
||||
}).catch((error) => {
|
||||
fail('Should not fail');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('fails with missing ids', (done) => {
|
||||
var input = {
|
||||
'deviceType': 'android',
|
||||
|
||||
@@ -23,6 +23,105 @@ describe('Parse.Query testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("notEqualTo with Relation is working", function(done) {
|
||||
var user = new Parse.User();
|
||||
user.setPassword("asdf");
|
||||
user.setUsername("zxcv");
|
||||
|
||||
var user1 = new Parse.User();
|
||||
user1.setPassword("asdf");
|
||||
user1.setUsername("qwerty");
|
||||
|
||||
var user2 = new Parse.User();
|
||||
user2.setPassword("asdf");
|
||||
user2.setUsername("asdf");
|
||||
|
||||
var Cake = Parse.Object.extend("Cake");
|
||||
var cake1 = new Cake();
|
||||
var cake2 = new Cake();
|
||||
var cake3 = new Cake();
|
||||
|
||||
|
||||
user.signUp().then(function(){
|
||||
return user1.signUp();
|
||||
}).then(function(){
|
||||
return user2.signUp();
|
||||
}).then(function(){
|
||||
var relLike1 = cake1.relation("liker");
|
||||
relLike1.add([user, user1]);
|
||||
|
||||
var relDislike1 = cake1.relation("hater");
|
||||
relDislike1.add(user2);
|
||||
return cake1.save();
|
||||
}).then(function(){
|
||||
var rellike2 = cake2.relation("liker");
|
||||
rellike2.add([user, user1]);
|
||||
|
||||
var relDislike2 = cake2.relation("hater");
|
||||
relDislike2.add(user2);
|
||||
|
||||
return cake2.save();
|
||||
}).then(function(){
|
||||
var rellike3 = cake3.relation("liker");
|
||||
rellike3.add(user);
|
||||
|
||||
var relDislike3 = cake3.relation("hater");
|
||||
relDislike3.add([user1, user2]);
|
||||
return cake3.save();
|
||||
}).then(function(){
|
||||
var query = new Parse.Query(Cake);
|
||||
// User2 likes nothing so we should receive 0
|
||||
query.equalTo("liker", user2);
|
||||
return query.find().then(function(results){
|
||||
equal(results.length, 0);
|
||||
});
|
||||
}).then(function(){
|
||||
var query = new Parse.Query(Cake);
|
||||
// User1 likes two of three cakes
|
||||
query.equalTo("liker", user1);
|
||||
return query.find().then(function(results){
|
||||
// It should return 2 -> cake 1 and cake 2
|
||||
equal(results.length, 2);
|
||||
});
|
||||
}).then(function(){
|
||||
var query = new Parse.Query(Cake);
|
||||
// We want to know which cake the user1 is not appreciating -> cake3
|
||||
query.notEqualTo("liker", user1);
|
||||
return query.find().then(function(results){
|
||||
// Should return 1 -> the cake 3
|
||||
equal(results.length, 1);
|
||||
});
|
||||
}).then(function(){
|
||||
var query = new Parse.Query(Cake);
|
||||
// User2 is a hater of everything so we should receive 0
|
||||
query.notEqualTo("hater", user2);
|
||||
return query.find().then(function(results){
|
||||
equal(results.length, 0);
|
||||
});
|
||||
}).then(function(){
|
||||
var query = new Parse.Query(Cake);
|
||||
// Only cake3 is liked by user
|
||||
query.notContainedIn("liker", [user1]);
|
||||
return query.find().then(function(results){
|
||||
equal(results.length, 1);
|
||||
});
|
||||
}).then(function(){
|
||||
var query = new Parse.Query(Cake);
|
||||
// All the users
|
||||
query.containedIn("liker", [user, user1, user2]);
|
||||
// Exclude user 1
|
||||
query.notEqualTo("liker", user1);
|
||||
// Only cake3 is liked only by user1
|
||||
return query.find().then(function(results){
|
||||
equal(results.length, 1);
|
||||
let cake = results[0];
|
||||
expect(cake.id).toBe(cake3.id);
|
||||
});
|
||||
}).then(function(){
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("query with limit", function(done) {
|
||||
var baz = new TestObject({ foo: 'baz' });
|
||||
var qux = new TestObject({ foo: 'qux' });
|
||||
@@ -1422,6 +1521,153 @@ describe('Parse.Query testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('properly includes array', (done) => {
|
||||
let objects = [];
|
||||
let total = 0;
|
||||
while(objects.length != 5) {
|
||||
let object = new Parse.Object('AnObject');
|
||||
object.set('key', objects.length);
|
||||
total += objects.length;
|
||||
objects.push(object);
|
||||
}
|
||||
Parse.Object.saveAll(objects).then(() => {
|
||||
let object = new Parse.Object("AContainer");
|
||||
object.set('objects', objects);
|
||||
return object.save();
|
||||
}).then(() => {
|
||||
let query = new Parse.Query('AContainer');
|
||||
query.include('objects');
|
||||
return query.find()
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
let res = results[0];
|
||||
let objects = res.get('objects');
|
||||
expect(objects.length).toBe(5);
|
||||
objects.forEach((object) => {
|
||||
total -= object.get('key');
|
||||
});
|
||||
expect(total).toBe(0);
|
||||
done()
|
||||
}, () => {
|
||||
fail('should not fail');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('properly includes array of mixed objects', (done) => {
|
||||
let objects = [];
|
||||
let total = 0;
|
||||
while(objects.length != 5) {
|
||||
let object = new Parse.Object('AnObject');
|
||||
object.set('key', objects.length);
|
||||
total += objects.length;
|
||||
objects.push(object);
|
||||
}
|
||||
while(objects.length != 10) {
|
||||
let object = new Parse.Object('AnotherObject');
|
||||
object.set('key', objects.length);
|
||||
total += objects.length;
|
||||
objects.push(object);
|
||||
}
|
||||
Parse.Object.saveAll(objects).then(() => {
|
||||
let object = new Parse.Object("AContainer");
|
||||
object.set('objects', objects);
|
||||
return object.save();
|
||||
}).then(() => {
|
||||
let query = new Parse.Query('AContainer');
|
||||
query.include('objects');
|
||||
return query.find()
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
let res = results[0];
|
||||
let objects = res.get('objects');
|
||||
expect(objects.length).toBe(10);
|
||||
objects.forEach((object) => {
|
||||
total -= object.get('key');
|
||||
});
|
||||
expect(total).toBe(0);
|
||||
done()
|
||||
}, (err) => {
|
||||
fail('should not fail');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('properly nested array of mixed objects with bad ids', (done) => {
|
||||
let objects = [];
|
||||
let total = 0;
|
||||
while(objects.length != 5) {
|
||||
let object = new Parse.Object('AnObject');
|
||||
object.set('key', objects.length);
|
||||
objects.push(object);
|
||||
}
|
||||
while(objects.length != 10) {
|
||||
let object = new Parse.Object('AnotherObject');
|
||||
object.set('key', objects.length);
|
||||
objects.push(object);
|
||||
}
|
||||
Parse.Object.saveAll(objects).then(() => {
|
||||
let object = new Parse.Object("AContainer");
|
||||
for (var i=0; i<objects.length; i++) {
|
||||
if (i%2 == 0) {
|
||||
objects[i].id = 'randomThing'
|
||||
} else {
|
||||
total += objects[i].get('key');
|
||||
}
|
||||
}
|
||||
object.set('objects', objects);
|
||||
return object.save();
|
||||
}).then(() => {
|
||||
let query = new Parse.Query('AContainer');
|
||||
query.include('objects');
|
||||
return query.find()
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
let res = results[0];
|
||||
let objects = res.get('objects');
|
||||
expect(objects.length).toBe(5);
|
||||
objects.forEach((object) => {
|
||||
total -= object.get('key');
|
||||
});
|
||||
expect(total).toBe(0);
|
||||
done()
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
fail('should not fail');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('properly fetches nested pointers', (done) => {
|
||||
let color = new Parse.Object('Color');
|
||||
color.set('hex','#133733');
|
||||
let circle = new Parse.Object('Circle');
|
||||
circle.set('radius', 1337);
|
||||
|
||||
Parse.Object.saveAll([color, circle]).then(() => {
|
||||
circle.set('color', color);
|
||||
let badCircle = new Parse.Object('Circle');
|
||||
badCircle.id = 'badId';
|
||||
let complexFigure = new Parse.Object('ComplexFigure');
|
||||
complexFigure.set('consistsOf', [circle, badCircle]);
|
||||
return complexFigure.save();
|
||||
}).then(() => {
|
||||
let q = new Parse.Query('ComplexFigure');
|
||||
q.include('consistsOf.color');
|
||||
return q.find()
|
||||
}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
let figure = results[0];
|
||||
expect(figure.get('consistsOf').length).toBe(1);
|
||||
expect(figure.get('consistsOf')[0].get('color').get('hex')).toBe('#133733');
|
||||
done();
|
||||
}, (err) => {
|
||||
fail('should not fail');
|
||||
done();
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
it("result object creation uses current extension", function(done) {
|
||||
var ParentObject = Parse.Object.extend({ className: "ParentObject" });
|
||||
// Add a foo() method to ChildObject.
|
||||
@@ -2209,4 +2455,81 @@ describe('Parse.Query testing', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('query with two OR subqueries (regression test #1259)', done => {
|
||||
let relatedObject = new Parse.Object('Class2');
|
||||
relatedObject.save().then(relatedObject => {
|
||||
let anObject = new Parse.Object('Class1');
|
||||
let relation = anObject.relation('relation');
|
||||
relation.add(relatedObject);
|
||||
return anObject.save();
|
||||
}).then(anObject => {
|
||||
let q1 = anObject.relation('relation').query();
|
||||
q1.doesNotExist('nonExistantKey1');
|
||||
let q2 = anObject.relation('relation').query();
|
||||
q2.doesNotExist('nonExistantKey2');
|
||||
let orQuery = Parse.Query.or(q1, q2).find().then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].objectId).toEqual(q1.objectId);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('objectId containedIn with multiple large array', done => {
|
||||
let obj = new Parse.Object('MyClass');
|
||||
obj.save().then(obj => {
|
||||
let longListOfStrings = [];
|
||||
for (let i = 0; i < 130; i++) {
|
||||
longListOfStrings.push(i.toString());
|
||||
}
|
||||
longListOfStrings.push(obj.id);
|
||||
let q = new Parse.Query('MyClass');
|
||||
q.containedIn('objectId', longListOfStrings);
|
||||
q.containedIn('objectId', longListOfStrings);
|
||||
return q.find();
|
||||
}).then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('include for specific object', function(done){
|
||||
var child = new Parse.Object('Child');
|
||||
var parent = new Parse.Object('Parent');
|
||||
child.set('foo', 'bar');
|
||||
parent.set('child', child);
|
||||
Parse.Object.saveAll([child, parent], function(response){
|
||||
var savedParent = response[1];
|
||||
var parentQuery = new Parse.Query('Parent');
|
||||
parentQuery.include('child');
|
||||
parentQuery.get(savedParent.id, {
|
||||
success: function(parentObj) {
|
||||
var childPointer = parentObj.get('child');
|
||||
ok(childPointer);
|
||||
equal(childPointer.get('foo'), 'bar');
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('select keys for specific object', function(done){
|
||||
var Foobar = new Parse.Object('Foobar');
|
||||
Foobar.set('foo', 'bar');
|
||||
Foobar.set('fizz', 'buzz');
|
||||
Foobar.save({
|
||||
success: function(savedFoobar){
|
||||
var foobarQuery = new Parse.Query('Foobar');
|
||||
foobarQuery.select('fizz');
|
||||
foobarQuery.get(savedFoobar.id,{
|
||||
success: function(foobarObj){
|
||||
equal(foobarObj.get('fizz'), 'buzz');
|
||||
equal(foobarObj.get('foo'), undefined);
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+62
-31
@@ -248,46 +248,50 @@ describe('Parse.Relation testing', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("queries on relation fields with multiple ins", (done) => {
|
||||
var ChildObject = Parse.Object.extend("ChildObject");
|
||||
var childObjects = [];
|
||||
for (var i = 0; i < 10; i++) {
|
||||
it("queries on relation fields with multiple containedIn (regression test for #1271)", (done) => {
|
||||
let ChildObject = Parse.Object.extend("ChildObject");
|
||||
let childObjects = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
childObjects.push(new ChildObject({x: i}));
|
||||
}
|
||||
|
||||
Parse.Object.saveAll(childObjects).then(() => {
|
||||
var ParentObject = Parse.Object.extend("ParentObject");
|
||||
var parent = new ParentObject();
|
||||
let ParentObject = Parse.Object.extend("ParentObject");
|
||||
let parent = new ParentObject();
|
||||
parent.set("x", 4);
|
||||
var relation = parent.relation("child");
|
||||
relation.add(childObjects[0]);
|
||||
relation.add(childObjects[1]);
|
||||
relation.add(childObjects[2]);
|
||||
var parent2 = new ParentObject();
|
||||
let parent1Children = parent.relation("child");
|
||||
parent1Children.add(childObjects[0]);
|
||||
parent1Children.add(childObjects[1]);
|
||||
parent1Children.add(childObjects[2]);
|
||||
let parent2 = new ParentObject();
|
||||
parent2.set("x", 3);
|
||||
var relation2 = parent2.relation("child");
|
||||
relation2.add(childObjects[4]);
|
||||
relation2.add(childObjects[5]);
|
||||
relation2.add(childObjects[6]);
|
||||
let parent2Children = parent2.relation("child");
|
||||
parent2Children.add(childObjects[4]);
|
||||
parent2Children.add(childObjects[5]);
|
||||
parent2Children.add(childObjects[6]);
|
||||
|
||||
var otherChild2 = parent2.relation("otherChild");
|
||||
otherChild2.add(childObjects[0]);
|
||||
otherChild2.add(childObjects[1]);
|
||||
otherChild2.add(childObjects[2]);
|
||||
let parent2OtherChildren = parent2.relation("otherChild");
|
||||
parent2OtherChildren.add(childObjects[0]);
|
||||
parent2OtherChildren.add(childObjects[1]);
|
||||
parent2OtherChildren.add(childObjects[2]);
|
||||
|
||||
var parents = [];
|
||||
parents.push(parent);
|
||||
parents.push(parent2);
|
||||
return Parse.Object.saveAll(parents);
|
||||
return Parse.Object.saveAll([parent, parent2]);
|
||||
}).then(() => {
|
||||
var query = new Parse.Query(ParentObject);
|
||||
var objects = [];
|
||||
objects.push(childObjects[0]);
|
||||
query.containedIn("child", objects);
|
||||
query.containedIn("otherChild", [childObjects[0]]);
|
||||
return query.find();
|
||||
}).then((list) => {
|
||||
equal(list.length, 2, "There should be 2 results");
|
||||
let objectsWithChild0InBothChildren = new Parse.Query(ParentObject);
|
||||
objectsWithChild0InBothChildren.containedIn("child", [childObjects[0]]);
|
||||
objectsWithChild0InBothChildren.containedIn("otherChild", [childObjects[0]]);
|
||||
return objectsWithChild0InBothChildren.find();
|
||||
}).then(objectsWithChild0InBothChildren => {
|
||||
//No parent has child 0 in both it's "child" and "otherChild" field;
|
||||
expect(objectsWithChild0InBothChildren.length).toEqual(0);
|
||||
}).then(() => {
|
||||
let objectsWithChild4andOtherChild1 = new Parse.Query(ParentObject);
|
||||
objectsWithChild4andOtherChild1.containedIn("child", [childObjects[4]]);
|
||||
objectsWithChild4andOtherChild1.containedIn("otherChild", [childObjects[1]]);
|
||||
return objectsWithChild4andOtherChild1.find();
|
||||
}).then(objects => {
|
||||
// parent2 has child 4 and otherChild 1
|
||||
expect(objects.length).toEqual(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -652,4 +656,31 @@ describe('Parse.Relation testing', () => {
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it('relations are not bidirectional (regression test for #871)', done => {
|
||||
let PersonObject = Parse.Object.extend("Person");
|
||||
let p1 = new PersonObject();
|
||||
let p2 = new PersonObject();
|
||||
Parse.Object.saveAll([p1, p2]).then(results => {
|
||||
let p1 = results[0];
|
||||
let p2 = results[1];
|
||||
let relation = p1.relation('relation');
|
||||
relation.add(p2);
|
||||
p1.save().then(() => {
|
||||
let query = new Parse.Query(PersonObject);
|
||||
query.equalTo('relation', p1);
|
||||
query.find().then(results => {
|
||||
expect(results.length).toEqual(0);
|
||||
|
||||
let query = new Parse.Query(PersonObject);
|
||||
query.equalTo('relation', p2);
|
||||
query.find().then(results => {
|
||||
expect(results.length).toEqual(1);
|
||||
expect(results[0].objectId).toEqual(p1.objectId);
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+86
-19
@@ -2,6 +2,7 @@
|
||||
|
||||
// Roles are not accessible without the master key, so they are not intended
|
||||
// for use by clients. We can manually test them using the master key.
|
||||
var RestQuery = require("../src/RestQuery");
|
||||
var Auth = require("../src/Auth").Auth;
|
||||
var Config = require("../src/Config");
|
||||
|
||||
@@ -60,21 +61,87 @@ describe('Parse Role testing', () => {
|
||||
|
||||
});
|
||||
|
||||
it("should recursively load roles", (done) => {
|
||||
|
||||
var rolesNames = ["FooRole", "BarRole", "BazRole"];
|
||||
|
||||
var createRole = function(name, sibling, user) {
|
||||
var role = new Parse.Role(name, new Parse.ACL());
|
||||
if (user) {
|
||||
var users = role.relation('users');
|
||||
users.add(user);
|
||||
}
|
||||
if (sibling) {
|
||||
role.relation('roles').add(sibling);
|
||||
}
|
||||
return role.save({}, { useMasterKey: true });
|
||||
var createRole = function(name, sibling, user) {
|
||||
var role = new Parse.Role(name, new Parse.ACL());
|
||||
if (user) {
|
||||
var users = role.relation('users');
|
||||
users.add(user);
|
||||
}
|
||||
if (sibling) {
|
||||
role.relation('roles').add(sibling);
|
||||
}
|
||||
return role.save({}, { useMasterKey: true });
|
||||
};
|
||||
|
||||
it("should not recursively load the same role multiple times", (done) => {
|
||||
var rootRole = "RootRole";
|
||||
var roleNames = ["FooRole", "BarRole", "BazRole"];
|
||||
var allRoles = [rootRole].concat(roleNames);
|
||||
|
||||
var roleObjs = {};
|
||||
var createAllRoles = function(user) {
|
||||
var promises = allRoles.map(function(roleName) {
|
||||
return createRole(roleName, null, user)
|
||||
.then(function(roleObj) {
|
||||
roleObjs[roleName] = roleObj;
|
||||
return roleObj;
|
||||
});
|
||||
});
|
||||
return Promise.all(promises);
|
||||
};
|
||||
|
||||
var restExecute = spyOn(RestQuery.prototype, "execute").and.callThrough();
|
||||
|
||||
var user,
|
||||
auth,
|
||||
getAllRolesSpy;
|
||||
createTestUser().then( (newUser) => {
|
||||
user = newUser;
|
||||
return createAllRoles(user);
|
||||
}).then ( (roles) => {
|
||||
var rootRoleObj = roleObjs[rootRole];
|
||||
roles.forEach(function(role, i) {
|
||||
// Add all roles to the RootRole
|
||||
if (role.id !== rootRoleObj.id) {
|
||||
role.relation("roles").add(rootRoleObj);
|
||||
}
|
||||
// Add all "roleNames" roles to the previous role
|
||||
if (i > 0) {
|
||||
role.relation("roles").add(roles[i - 1]);
|
||||
}
|
||||
});
|
||||
|
||||
return Parse.Object.saveAll(roles, { useMasterKey: true });
|
||||
}).then( () => {
|
||||
auth = new Auth({config: new Config("test"), isMaster: true, user: user});
|
||||
getAllRolesSpy = spyOn(auth, "_getAllRoleNamesForId").and.callThrough();
|
||||
|
||||
return auth._loadRoles();
|
||||
}).then ( (roles) => {
|
||||
expect(roles.length).toEqual(4);
|
||||
|
||||
allRoles.forEach(function(name) {
|
||||
expect(roles.indexOf("role:"+name)).not.toBe(-1);
|
||||
});
|
||||
|
||||
// 1 Query for the initial setup
|
||||
// 4 Queries for all the specific roles
|
||||
// 1 Query for the final $in
|
||||
expect(restExecute.calls.count()).toEqual(6);
|
||||
|
||||
// 4 One query for each of the roles
|
||||
// 3 Queries for the "RootRole"
|
||||
expect(getAllRolesSpy.calls.count()).toEqual(7);
|
||||
done()
|
||||
}).catch( () => {
|
||||
fail("should succeed");
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("should recursively load roles", (done) => {
|
||||
var rolesNames = ["FooRole", "BarRole", "BazRole"];
|
||||
var roleIds = {};
|
||||
createTestUser().then( (user) => {
|
||||
// Put the user on the 1st role
|
||||
@@ -97,7 +164,7 @@ describe('Parse Role testing', () => {
|
||||
expect(roles.length).toEqual(3);
|
||||
rolesNames.forEach( (name) => {
|
||||
expect(roles.indexOf('role:'+name)).not.toBe(-1);
|
||||
})
|
||||
});
|
||||
done();
|
||||
}, function(err){
|
||||
fail("should succeed")
|
||||
@@ -122,7 +189,7 @@ describe('Parse Role testing', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("Should properly resolve roles", (done) => {
|
||||
let admin = new Parse.Role("Admin", new Parse.ACL());
|
||||
let moderator = new Parse.Role("Moderator", new Parse.ACL());
|
||||
@@ -134,7 +201,7 @@ describe('Parse Role testing', () => {
|
||||
moderator.getRoles().add([admin, superModerator]);
|
||||
superContentManager.getRoles().add(superModerator);
|
||||
return Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true});
|
||||
}).then(() => {
|
||||
}).then(() => {
|
||||
var auth = new Auth({ config: new Config("test"), isMaster: true });
|
||||
// For each role, fetch their sibling, what they inherit
|
||||
// return with result and roleId for later comparison
|
||||
@@ -147,7 +214,7 @@ describe('Parse Role testing', () => {
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
return Parse.Promise.when(promises);
|
||||
}).then((results) => {
|
||||
results.forEach((result) => {
|
||||
@@ -174,7 +241,7 @@ describe('Parse Role testing', () => {
|
||||
console.error(err);
|
||||
done();
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
||||
it('can create role and query empty users', (done)=> {
|
||||
|
||||
@@ -26,6 +26,7 @@ function verifyACL(user) {
|
||||
}
|
||||
|
||||
describe('Parse.User testing', () => {
|
||||
|
||||
it("user sign up class method", (done) => {
|
||||
Parse.User.signUp("asdf", "zxcv", null, {
|
||||
success: function(user) {
|
||||
@@ -2160,4 +2161,44 @@ describe('Parse.User testing', () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('should fail to become user with expired token', (done) => {
|
||||
Parse.User.signUp("auser", "somepass", null, {
|
||||
success: function(user) {
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/classes/_Session',
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
},
|
||||
}, (error, response, body) => {
|
||||
var id = body.results[0].objectId;
|
||||
var expiresAt = new Date((new Date()).setYear(2015));
|
||||
var token = body.results[0].sessionToken;
|
||||
request.put({
|
||||
url: "http://localhost:8378/1/classes/_Session/" + id,
|
||||
json: true,
|
||||
headers: {
|
||||
'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-Master-Key': 'test',
|
||||
},
|
||||
body: {
|
||||
expiresAt: { __type: "Date", iso: expiresAt.toISOString() },
|
||||
},
|
||||
}, (error, response, body) => {
|
||||
Parse.User.become(token)
|
||||
.then(() => { fail("Should not have succeded"); })
|
||||
.fail((err) => {
|
||||
expect(err.code).toEqual(209);
|
||||
expect(err.message).toEqual("Session token is expired.");
|
||||
Parse.User.logOut() // Logout to prevent polluting CLI with messages
|
||||
.then(done());
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -317,6 +317,45 @@ describe('PushController', () => {
|
||||
|
||||
});
|
||||
|
||||
it('should properly report failures in _PushStatus', (done) => {
|
||||
var pushAdapter = {
|
||||
send: function(body, installations) {
|
||||
return installations.map((installation) => {
|
||||
return Promise.resolve({
|
||||
deviceType: installation.deviceType
|
||||
})
|
||||
})
|
||||
},
|
||||
getValidPushTypes: function() {
|
||||
return ["ios"];
|
||||
}
|
||||
}
|
||||
let where = { 'channels': {
|
||||
'$ins': ['Giants', 'Mets']
|
||||
}};
|
||||
var payload = {data: {
|
||||
alert: "Hello World!",
|
||||
badge: 1,
|
||||
}}
|
||||
var config = new Config(Parse.applicationId);
|
||||
var auth = {
|
||||
isMaster: true
|
||||
}
|
||||
var pushController = new PushController(pushAdapter, Parse.applicationId);
|
||||
pushController.sendPush(payload, where, config, auth).then(() => {
|
||||
fail('should not succeed');
|
||||
done();
|
||||
}).catch(() => {
|
||||
let query = new Parse.Query('_PushStatus');
|
||||
query.find({useMasterKey: true}).then((results) => {
|
||||
expect(results.length).toBe(1);
|
||||
let pushStatus = results[0];
|
||||
expect(pushStatus.get('status')).toBe('failed');
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
it('should support full RESTQuery for increment', (done) => {
|
||||
var payload = {data: {
|
||||
alert: "Hello World!",
|
||||
|
||||
@@ -112,6 +112,20 @@ describe('matchesQuery', function() {
|
||||
expect(matchesQuery(obj, q)).toBe(false);
|
||||
});
|
||||
|
||||
it('matches queries with doesNotExist constraint', function() {
|
||||
var obj = {
|
||||
id: new Id('Item', 'O1'),
|
||||
count: 15
|
||||
};
|
||||
var q = new Parse.Query('Item');
|
||||
q.doesNotExist('name');
|
||||
expect(matchesQuery(obj, q)).toBe(true);
|
||||
|
||||
q = new Parse.Query('Item');
|
||||
q.doesNotExist('count');
|
||||
expect(matchesQuery(obj, q)).toBe(false);
|
||||
});
|
||||
|
||||
it('matches on equality queries', function() {
|
||||
var day = new Date();
|
||||
var location = new Parse.GeoPoint({
|
||||
|
||||
@@ -284,4 +284,72 @@ describe('rest create', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("test default session length", (done) => {
|
||||
var user = {
|
||||
username: 'asdf',
|
||||
password: 'zxcv',
|
||||
foo: 'bar',
|
||||
};
|
||||
var now = new Date();
|
||||
|
||||
rest.create(config, auth.nobody(config), '_User', user)
|
||||
.then((r) => {
|
||||
expect(Object.keys(r.response).length).toEqual(3);
|
||||
expect(typeof r.response.objectId).toEqual('string');
|
||||
expect(typeof r.response.createdAt).toEqual('string');
|
||||
expect(typeof r.response.sessionToken).toEqual('string');
|
||||
return rest.find(config, auth.master(config),
|
||||
'_Session', {sessionToken: r.response.sessionToken});
|
||||
})
|
||||
.then((r) => {
|
||||
expect(r.results.length).toEqual(1);
|
||||
|
||||
var session = r.results[0];
|
||||
var actual = new Date(session.expiresAt.iso);
|
||||
var expected = new Date(now.getTime() + (1000 * 3600 * 24 * 365));
|
||||
|
||||
expect(actual.getFullYear()).toEqual(expected.getFullYear());
|
||||
expect(actual.getMonth()).toEqual(expected.getMonth());
|
||||
expect(actual.getDate()).toEqual(expected.getDate());
|
||||
expect(actual.getMinutes()).toEqual(expected.getMinutes());
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("test specified session length", (done) => {
|
||||
var user = {
|
||||
username: 'asdf',
|
||||
password: 'zxcv',
|
||||
foo: 'bar',
|
||||
};
|
||||
var sessionLength = 3600, // 1 Hour ahead
|
||||
now = new Date(); // For reference later
|
||||
config.sessionLength = sessionLength;
|
||||
|
||||
rest.create(config, auth.nobody(config), '_User', user)
|
||||
.then((r) => {
|
||||
expect(Object.keys(r.response).length).toEqual(3);
|
||||
expect(typeof r.response.objectId).toEqual('string');
|
||||
expect(typeof r.response.createdAt).toEqual('string');
|
||||
expect(typeof r.response.sessionToken).toEqual('string');
|
||||
return rest.find(config, auth.master(config),
|
||||
'_Session', {sessionToken: r.response.sessionToken});
|
||||
})
|
||||
.then((r) => {
|
||||
expect(r.results.length).toEqual(1);
|
||||
|
||||
var session = r.results[0];
|
||||
var actual = new Date(session.expiresAt.iso);
|
||||
var expected = new Date(now.getTime() + (sessionLength*1000));
|
||||
|
||||
expect(actual.getFullYear()).toEqual(expected.getFullYear());
|
||||
expect(actual.getMonth()).toEqual(expected.getMonth());
|
||||
expect(actual.getDate()).toEqual(expected.getDate());
|
||||
expect(actual.getHours()).toEqual(expected.getHours());
|
||||
expect(actual.getMinutes()).toEqual(expected.getMinutes());
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"arg1": "my_app",
|
||||
"arg2": "8888",
|
||||
"arg3": "hello",
|
||||
"arg4": "/1"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"arg1": "my_app",
|
||||
"arg2": 8888,
|
||||
"arg3": "hello",
|
||||
"arg4": "/1"
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"arg1": "my_app",
|
||||
"arg2": "hello",
|
||||
"arg3": "hello",
|
||||
"arg4": "/1"
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"apps": [
|
||||
{
|
||||
"arg1": "my_app",
|
||||
"arg2": "99999",
|
||||
"arg3": "hello",
|
||||
"arg4": "/1"
|
||||
},
|
||||
{
|
||||
"arg1": "my_app2",
|
||||
"arg2": "9999",
|
||||
"arg3": "hello",
|
||||
"arg4": "/1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"arg1": "my_app",
|
||||
"arg2": "8888",
|
||||
"arg3": "hello",
|
||||
"myArg": "/1"
|
||||
}
|
||||
+1
-1
@@ -10,7 +10,7 @@ var ParseServer = require('../src/index').ParseServer;
|
||||
var path = require('path');
|
||||
|
||||
var databaseURI = process.env.DATABASE_URI;
|
||||
var cloudMain = process.env.CLOUD_CODE_MAIN || '../spec/cloud/main.js';
|
||||
var cloudMain = process.env.CLOUD_CODE_MAIN || './spec/cloud/main.js';
|
||||
var port = 8378;
|
||||
|
||||
// Default server configuration for tests.
|
||||
|
||||
@@ -2,6 +2,7 @@ var request = require('request');
|
||||
var parseServerPackage = require('../package.json');
|
||||
var MockEmailAdapterWithOptions = require('./MockEmailAdapterWithOptions');
|
||||
var ParseServer = require("../src/index");
|
||||
var Config = require('../src/Config');
|
||||
var express = require('express');
|
||||
|
||||
describe('server', () => {
|
||||
@@ -174,6 +175,26 @@ describe('server', () => {
|
||||
})
|
||||
});
|
||||
|
||||
it('can load absolute cloud code file', done => {
|
||||
setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
appId: 'test',
|
||||
masterKey: 'test',
|
||||
cloud: __dirname + '/cloud/main.js'
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('can load relative cloud code file', done => {
|
||||
setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
appId: 'test',
|
||||
masterKey: 'test',
|
||||
cloud: './spec/cloud/main.js'
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('can create a parse-server', done => {
|
||||
var parseServer = new ParseServer.default({
|
||||
appId: "aTestApp",
|
||||
@@ -246,4 +267,70 @@ describe('server', () => {
|
||||
expect(ParseServer.FileSystemAdapter).toThrow();
|
||||
done();
|
||||
});
|
||||
|
||||
it('properly gives publicServerURL when set', done => {
|
||||
setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
appId: 'test',
|
||||
masterKey: 'test',
|
||||
publicServerURL: 'https://myserver.com/1'
|
||||
});
|
||||
var config = new Config('test', 'http://localhost:8378/1');
|
||||
expect(config.mount).toEqual('https://myserver.com/1');
|
||||
done();
|
||||
});
|
||||
|
||||
it('properly removes trailing slash in mount', done => {
|
||||
setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
appId: 'test',
|
||||
masterKey: 'test'
|
||||
});
|
||||
var config = new Config('test', 'http://localhost:8378/1/');
|
||||
expect(config.mount).toEqual('http://localhost:8378/1');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should throw when getting invalid mount', done => {
|
||||
expect(() => setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
appId: 'test',
|
||||
masterKey: 'test',
|
||||
publicServerURL: 'blabla:/some'
|
||||
}) ).toThrow("publicServerURL should be a valid HTTPS URL starting with https://");
|
||||
done();
|
||||
});
|
||||
|
||||
it('fails if the session length is not a number', (done) => {
|
||||
expect(() => setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
appId: 'test',
|
||||
appName: 'unused',
|
||||
javascriptKey: 'test',
|
||||
masterKey: 'test',
|
||||
sessionLength: 'test'
|
||||
})).toThrow('Session length must be a valid number.');
|
||||
done();
|
||||
});
|
||||
|
||||
it('fails if the session length is less than or equal to 0', (done) => {
|
||||
expect(() => setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
appId: 'test',
|
||||
appName: 'unused',
|
||||
javascriptKey: 'test',
|
||||
masterKey: 'test',
|
||||
sessionLength: '-33'
|
||||
})).toThrow('Session length must be a value greater than 0.');
|
||||
|
||||
expect(() => setServerConfiguration({
|
||||
serverURL: 'http://localhost:8378/1',
|
||||
appId: 'test',
|
||||
appName: 'unused',
|
||||
javascriptKey: 'test',
|
||||
masterKey: 'test',
|
||||
sessionLength: '0'
|
||||
})).toThrow('Session length must be a value greater than 0.');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
+64
-33
@@ -981,7 +981,7 @@ describe('schemas', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should not be able to add a field', done => {
|
||||
request.post({
|
||||
url: 'http://localhost:8378/1/schemas/AClass',
|
||||
@@ -1010,7 +1010,7 @@ describe('schemas', () => {
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should not be able to add a field', done => {
|
||||
request.post({
|
||||
url: 'http://localhost:8378/1/schemas/AClass',
|
||||
@@ -1038,7 +1038,7 @@ describe('schemas', () => {
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should throw with invalid userId (>10 chars)', done => {
|
||||
request.post({
|
||||
url: 'http://localhost:8378/1/schemas/AClass',
|
||||
@@ -1056,7 +1056,7 @@ describe('schemas', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should throw with invalid userId (<10 chars)', done => {
|
||||
request.post({
|
||||
url: 'http://localhost:8378/1/schemas/AClass',
|
||||
@@ -1074,7 +1074,7 @@ describe('schemas', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should throw with invalid userId (invalid char)', done => {
|
||||
request.post({
|
||||
url: 'http://localhost:8378/1/schemas/AClass',
|
||||
@@ -1092,7 +1092,7 @@ describe('schemas', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should throw with invalid * (spaces)', done => {
|
||||
request.post({
|
||||
url: 'http://localhost:8378/1/schemas/AClass',
|
||||
@@ -1110,7 +1110,7 @@ describe('schemas', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should throw with invalid * (spaces)', done => {
|
||||
request.post({
|
||||
url: 'http://localhost:8378/1/schemas/AClass',
|
||||
@@ -1128,7 +1128,7 @@ describe('schemas', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should throw with invalid value', done => {
|
||||
request.post({
|
||||
url: 'http://localhost:8378/1/schemas/AClass',
|
||||
@@ -1146,7 +1146,7 @@ describe('schemas', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('should throw with invalid value', done => {
|
||||
request.post({
|
||||
url: 'http://localhost:8378/1/schemas/AClass',
|
||||
@@ -1164,10 +1164,10 @@ describe('schemas', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
function setPermissionsOnClass(className, permissions, doPut) {
|
||||
let op = request.post;
|
||||
if (doPut)
|
||||
if (doPut)
|
||||
{
|
||||
op = request.put;
|
||||
}
|
||||
@@ -1190,18 +1190,18 @@ describe('schemas', () => {
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
it('validate CLP 1', done => {
|
||||
let user = new Parse.User();
|
||||
user.setUsername('user');
|
||||
user.setPassword('user');
|
||||
|
||||
|
||||
let admin = new Parse.User();
|
||||
admin.setUsername('admin');
|
||||
admin.setPassword('admin');
|
||||
|
||||
|
||||
let role = new Parse.Role('admin', new Parse.ACL());
|
||||
|
||||
|
||||
setPermissionsOnClass('AClass', {
|
||||
'find': {
|
||||
'role:admin': true
|
||||
@@ -1239,18 +1239,18 @@ describe('schemas', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('validate CLP 2', done => {
|
||||
let user = new Parse.User();
|
||||
user.setUsername('user');
|
||||
user.setPassword('user');
|
||||
|
||||
|
||||
let admin = new Parse.User();
|
||||
admin.setUsername('admin');
|
||||
admin.setPassword('admin');
|
||||
|
||||
|
||||
let role = new Parse.Role('admin', new Parse.ACL());
|
||||
|
||||
|
||||
setPermissionsOnClass('AClass', {
|
||||
'find': {
|
||||
'role:admin': true
|
||||
@@ -1304,18 +1304,18 @@ describe('schemas', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('validate CLP 3', done => {
|
||||
let user = new Parse.User();
|
||||
user.setUsername('user');
|
||||
user.setPassword('user');
|
||||
|
||||
|
||||
let admin = new Parse.User();
|
||||
admin.setUsername('admin');
|
||||
admin.setPassword('admin');
|
||||
|
||||
|
||||
let role = new Parse.Role('admin', new Parse.ACL());
|
||||
|
||||
|
||||
setPermissionsOnClass('AClass', {
|
||||
'find': {
|
||||
'role:admin': true
|
||||
@@ -1362,18 +1362,18 @@ describe('schemas', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('validate CLP 4', done => {
|
||||
let user = new Parse.User();
|
||||
user.setUsername('user');
|
||||
user.setPassword('user');
|
||||
|
||||
|
||||
let admin = new Parse.User();
|
||||
admin.setUsername('admin');
|
||||
admin.setPassword('admin');
|
||||
|
||||
|
||||
let role = new Parse.Role('admin', new Parse.ACL());
|
||||
|
||||
|
||||
setPermissionsOnClass('AClass', {
|
||||
'find': {
|
||||
'role:admin': true
|
||||
@@ -1400,7 +1400,7 @@ describe('schemas', () => {
|
||||
// borked CLP should not affec security
|
||||
return setPermissionsOnClass('AClass', {
|
||||
'found': {
|
||||
'role:admin': true
|
||||
'role:admin': true
|
||||
}
|
||||
}, true).then(() => {
|
||||
fail("Should not be able to save a borked CLP");
|
||||
@@ -1430,21 +1430,21 @@ describe('schemas', () => {
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
it('validate CLP 5', done => {
|
||||
let user = new Parse.User();
|
||||
user.setUsername('user');
|
||||
user.setPassword('user');
|
||||
|
||||
|
||||
let user2 = new Parse.User();
|
||||
user2.setUsername('user2');
|
||||
user2.setPassword('user2');
|
||||
let admin = new Parse.User();
|
||||
admin.setUsername('admin');
|
||||
admin.setPassword('admin');
|
||||
|
||||
|
||||
let role = new Parse.Role('admin', new Parse.ACL());
|
||||
|
||||
|
||||
Promise.resolve().then(() => {
|
||||
return Parse.Object.saveAll([user, user2, admin, role], {useMasterKey: true});
|
||||
}).then(()=> {
|
||||
@@ -1495,5 +1495,36 @@ describe('schemas', () => {
|
||||
}).then(() => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('can add field as master (issue #1257)', (done) => {
|
||||
setPermissionsOnClass('AClass', {
|
||||
'addField': {}
|
||||
}).then(() => {
|
||||
var obj = new Parse.Object('AClass');
|
||||
obj.set('key', 'value');
|
||||
return obj.save(null, {useMasterKey: true})
|
||||
}).then((obj) => {
|
||||
expect(obj.get('key')).toEqual('value');
|
||||
done();
|
||||
}, (err) => {
|
||||
fail('should not fail');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can login when addFields is false (issue #1355)', (done) => {
|
||||
setPermissionsOnClass('_User', {
|
||||
'addField': {}
|
||||
}).then(() => {
|
||||
return Parse.User.signUp('foo', 'bar');
|
||||
}).then((user) => {
|
||||
expect(user.getUsername()).toBe('foo');
|
||||
done()
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
fail('should create user');
|
||||
done();
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
+17
-5
@@ -62,6 +62,13 @@ var getAuthForSessionToken = function({ config, sessionToken, installationId } =
|
||||
if (results.length !== 1 || !results[0]['user']) {
|
||||
return nobody(config);
|
||||
}
|
||||
|
||||
var now = new Date(),
|
||||
expiresAt = new Date(results[0].expiresAt.iso);
|
||||
if(expiresAt < now) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN,
|
||||
'Session token is expired.');
|
||||
}
|
||||
var obj = results[0]['user'];
|
||||
delete obj.password;
|
||||
obj['className'] = '_User';
|
||||
@@ -110,8 +117,9 @@ Auth.prototype._loadRoles = function() {
|
||||
|
||||
var roleIDs = results.map(r => r.objectId);
|
||||
var promises = [Promise.resolve(roleIDs)];
|
||||
var queriedRoles = {};
|
||||
for (var role of roleIDs) {
|
||||
promises.push(this._getAllRoleNamesForId(role));
|
||||
promises.push(this._getAllRoleNamesForId(role, queriedRoles));
|
||||
}
|
||||
return Promise.all(promises).then((results) => {
|
||||
var allIDs = [];
|
||||
@@ -139,8 +147,12 @@ Auth.prototype._loadRoles = function() {
|
||||
};
|
||||
|
||||
// Given a role object id, get any other roles it is part of
|
||||
Auth.prototype._getAllRoleNamesForId = function(roleID) {
|
||||
|
||||
Auth.prototype._getAllRoleNamesForId = function(roleID, queriedRoles = {}) {
|
||||
// Don't need to requery this role as it is already being queried for.
|
||||
if (queriedRoles[roleID] != null) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
queriedRoles[roleID] = true;
|
||||
// As per documentation, a Role inherits AnotherRole
|
||||
// if this Role is in the roles pointer of this AnotherRole
|
||||
// Let's find all the roles where this role is in a roles relation
|
||||
@@ -160,13 +172,13 @@ Auth.prototype._getAllRoleNamesForId = function(roleID) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
var roleIDs = results.map(r => r.objectId);
|
||||
|
||||
|
||||
// we found a list of roles where the roleID
|
||||
// is referenced in the roles relation,
|
||||
// Get the roles where those found roles are also
|
||||
// referenced the same way
|
||||
var parentRolesPromises = roleIDs.map( (roleId) => {
|
||||
return this._getAllRoleNamesForId(roleId);
|
||||
return this._getAllRoleNamesForId(roleId, queriedRoles);
|
||||
});
|
||||
parentRolesPromises.push(Promise.resolve(roleIDs));
|
||||
return Promise.all(parentRolesPromises);
|
||||
|
||||
+49
-4
@@ -4,6 +4,16 @@
|
||||
|
||||
import cache from './cache';
|
||||
|
||||
function removeTrailingSlash(str) {
|
||||
if (!str) {
|
||||
return str;
|
||||
}
|
||||
if (str.endsWith("/")) {
|
||||
str = str.substr(0, str.length-1);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
export class Config {
|
||||
constructor(applicationId: string, mount: string) {
|
||||
let DatabaseAdapter = require('./DatabaseAdapter');
|
||||
@@ -24,7 +34,7 @@ export class Config {
|
||||
this.database = DatabaseAdapter.getDatabaseConnection(applicationId, cacheInfo.collectionPrefix);
|
||||
|
||||
this.serverURL = cacheInfo.serverURL;
|
||||
this.publicServerURL = cacheInfo.publicServerURL;
|
||||
this.publicServerURL = removeTrailingSlash(cacheInfo.publicServerURL);
|
||||
this.verifyUserEmails = cacheInfo.verifyUserEmails;
|
||||
this.appName = cacheInfo.appName;
|
||||
|
||||
@@ -35,14 +45,23 @@ export class Config {
|
||||
this.userController = cacheInfo.userController;
|
||||
this.authDataManager = cacheInfo.authDataManager;
|
||||
this.customPages = cacheInfo.customPages || {};
|
||||
this.mount = mount;
|
||||
this.mount = removeTrailingSlash(mount);
|
||||
this.liveQueryController = cacheInfo.liveQueryController;
|
||||
this.sessionLength = cacheInfo.sessionLength;
|
||||
this.generateSessionExpiresAt = this.generateSessionExpiresAt.bind(this);
|
||||
}
|
||||
|
||||
static validate(options) {
|
||||
this.validateEmailConfiguration({verifyUserEmails: options.verifyUserEmails,
|
||||
appName: options.appName,
|
||||
this.validateEmailConfiguration({verifyUserEmails: options.verifyUserEmails,
|
||||
appName: options.appName,
|
||||
publicServerURL: options.publicServerURL})
|
||||
if (options.publicServerURL) {
|
||||
if (!options.publicServerURL.startsWith("http://") && !options.publicServerURL.startsWith("https://")) {
|
||||
throw "publicServerURL should be a valid HTTPS URL starting with https://"
|
||||
}
|
||||
}
|
||||
|
||||
this.validateSessionLength(options.sessionLength);
|
||||
}
|
||||
|
||||
static validateEmailConfiguration({verifyUserEmails, appName, publicServerURL}) {
|
||||
@@ -56,6 +75,32 @@ export class Config {
|
||||
}
|
||||
}
|
||||
|
||||
get mount() {
|
||||
var mount = this._mount;
|
||||
if (this.publicServerURL) {
|
||||
mount = this.publicServerURL;
|
||||
}
|
||||
return mount;
|
||||
}
|
||||
|
||||
set mount(newValue) {
|
||||
this._mount = newValue;
|
||||
}
|
||||
|
||||
static validateSessionLength(sessionLength) {
|
||||
if(isNaN(sessionLength)) {
|
||||
throw 'Session length must be a valid number.';
|
||||
}
|
||||
else if(sessionLength <= 0) {
|
||||
throw 'Session length must be a value greater than 0.'
|
||||
}
|
||||
}
|
||||
|
||||
generateSessionExpiresAt() {
|
||||
var now = new Date();
|
||||
return new Date(now.getTime() + (this.sessionLength*1000));
|
||||
}
|
||||
|
||||
get invalidLinkURL() {
|
||||
return this.customPages.invalidLink || `${this.publicServerURL}/apps/invalid_link.html`;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// A database adapter that works with data exported from the hosted
|
||||
// Parse database.
|
||||
|
||||
import intersect from 'intersect';
|
||||
|
||||
var mongodb = require('mongodb');
|
||||
var Parse = require('parse/node').Parse;
|
||||
|
||||
@@ -103,9 +105,14 @@ DatabaseController.prototype.redirectClassNameForKey = function(className, key)
|
||||
// batch request, that could confuse other users of the schema.
|
||||
DatabaseController.prototype.validateObject = function(className, object, query, options) {
|
||||
let schema;
|
||||
let isMaster = !('acl' in options);
|
||||
var aclGroup = options.acl || [];
|
||||
return this.loadSchema().then(s => {
|
||||
schema = s;
|
||||
return this.canAddField(schema, className, object, options.acl || []);
|
||||
if (isMaster) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.canAddField(schema, className, object, aclGroup);
|
||||
}).then(() => {
|
||||
return schema.validateObject(className, object, query);
|
||||
});
|
||||
@@ -437,26 +444,60 @@ DatabaseController.prototype.reduceInRelation = function(className, query, schem
|
||||
}
|
||||
|
||||
let promises = Object.keys(query).map((key) => {
|
||||
if (query[key] && (query[key]['$in'] || query[key].__type == 'Pointer')) {
|
||||
if (query[key] && (query[key]['$in'] || query[key]['$ne'] || query[key]['$nin'] || query[key].__type == 'Pointer')) {
|
||||
let t = schema.getExpectedType(className, key);
|
||||
let match = t ? t.match(/^relation<(.*)>$/) : false;
|
||||
if (!match) {
|
||||
return Promise.resolve(query);
|
||||
}
|
||||
let relatedClassName = match[1];
|
||||
let relatedIds;
|
||||
if (query[key]['$in']) {
|
||||
relatedIds = query[key]['$in'].map(r => r.objectId);
|
||||
} else {
|
||||
relatedIds = [query[key].objectId];
|
||||
}
|
||||
return this.owningIds(className, key, relatedIds).then((ids) => {
|
||||
delete query[key];
|
||||
this.addInObjectIdsIds(ids, query);
|
||||
return Promise.resolve(query);
|
||||
// Build the list of queries
|
||||
let queries = Object.keys(query[key]).map((constraintKey) => {
|
||||
let relatedIds;
|
||||
let isNegation = false;
|
||||
if (constraintKey === 'objectId') {
|
||||
relatedIds = [query[key].objectId];
|
||||
} else if (constraintKey == '$in') {
|
||||
relatedIds = query[key]['$in'].map(r => r.objectId);
|
||||
} else if (constraintKey == '$nin') {
|
||||
isNegation = true;
|
||||
relatedIds = query[key]['$nin'].map(r => r.objectId);
|
||||
} else if (constraintKey == '$ne') {
|
||||
isNegation = true;
|
||||
relatedIds = [query[key]['$ne'].objectId];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
isNegation,
|
||||
relatedIds
|
||||
}
|
||||
});
|
||||
|
||||
// remove the current queryKey as we don,t need it anymore
|
||||
delete query[key];
|
||||
// execute each query independnently to build the list of
|
||||
// $in / $nin
|
||||
let promises = queries.map((q) => {
|
||||
if (!q) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.owningIds(className, key, q.relatedIds).then((ids) => {
|
||||
if (q.isNegation) {
|
||||
this.addNotInObjectIdsIds(ids, query);
|
||||
} else {
|
||||
this.addInObjectIdsIds(ids, query);
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
return Promise.resolve();
|
||||
})
|
||||
|
||||
}
|
||||
return Promise.resolve(query);
|
||||
return Promise.resolve();
|
||||
})
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
@@ -487,18 +528,51 @@ DatabaseController.prototype.reduceRelationKeys = function(className, query) {
|
||||
}
|
||||
};
|
||||
|
||||
DatabaseController.prototype.addInObjectIdsIds = function(ids, query) {
|
||||
if (typeof query.objectId == 'string') {
|
||||
// Add equality op as we are sure
|
||||
// we had a constraint on that one
|
||||
query.objectId = {'$eq': query.objectId};
|
||||
DatabaseController.prototype.addInObjectIdsIds = function(ids = null, query) {
|
||||
let idsFromString = typeof query.objectId === 'string' ? [query.objectId] : null;
|
||||
let idsFromEq = query.objectId && query.objectId['$eq'] ? [query.objectId['$eq']] : null;
|
||||
let idsFromIn = query.objectId && query.objectId['$in'] ? query.objectId['$in'] : null;
|
||||
|
||||
let allIds = [idsFromString, idsFromEq, idsFromIn, ids].filter(list => list !== null);
|
||||
let totalLength = allIds.reduce((memo, list) => memo + list.length, 0);
|
||||
|
||||
let idsIntersection = [];
|
||||
if (totalLength > 125) {
|
||||
idsIntersection = intersect.big(allIds);
|
||||
} else {
|
||||
idsIntersection = intersect(allIds);
|
||||
}
|
||||
query.objectId = query.objectId || {};
|
||||
let queryIn = [].concat(query.objectId['$in'] || [], ids || []);
|
||||
// make a set and spread to remove duplicates
|
||||
// replace the $in operator as other constraints
|
||||
// may be set
|
||||
query.objectId['$in'] = [...new Set(queryIn)];
|
||||
|
||||
// Need to make sure we don't clobber existing $lt or other constraints on objectId.
|
||||
// Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints
|
||||
// is expected though.
|
||||
if (!('objectId' in query) || typeof query.objectId === 'string') {
|
||||
query.objectId = {};
|
||||
}
|
||||
query.objectId['$in'] = idsIntersection;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
DatabaseController.prototype.addNotInObjectIdsIds = function(ids = null, query) {
|
||||
let idsFromNin = query.objectId && query.objectId['$nin'] ? query.objectId['$nin'] : null;
|
||||
let allIds = [idsFromNin, ids].filter(list => list !== null);
|
||||
let totalLength = allIds.reduce((memo, list) => memo + list.length, 0);
|
||||
|
||||
let idsIntersection = [];
|
||||
if (totalLength > 125) {
|
||||
idsIntersection = intersect.big(allIds);
|
||||
} else {
|
||||
idsIntersection = intersect(allIds);
|
||||
}
|
||||
|
||||
// Need to make sure we don't clobber existing $lt or other constraints on objectId.
|
||||
// Clobbering $eq, $in and shorthand $eq (query.objectId === 'string') constraints
|
||||
// is expected though.
|
||||
if (!('objectId' in query) || typeof query.objectId === 'string') {
|
||||
query.objectId = {};
|
||||
}
|
||||
query.objectId['$nin'] = idsIntersection;
|
||||
|
||||
return query;
|
||||
}
|
||||
@@ -518,7 +592,7 @@ DatabaseController.prototype.addInObjectIdsIds = function(ids, query) {
|
||||
// anything about users, ideally. Then, improve the format of the ACL
|
||||
// arg to work like the others.
|
||||
DatabaseController.prototype.find = function(className, query, options = {}) {
|
||||
var mongoOptions = {};
|
||||
let mongoOptions = {};
|
||||
if (options.skip) {
|
||||
mongoOptions.skip = options.skip;
|
||||
}
|
||||
@@ -526,45 +600,39 @@ DatabaseController.prototype.find = function(className, query, options = {}) {
|
||||
mongoOptions.limit = options.limit;
|
||||
}
|
||||
|
||||
var isMaster = !('acl' in options);
|
||||
var aclGroup = options.acl || [];
|
||||
var acceptor = function(schema) {
|
||||
return schema.hasKeys(className, keysForQuery(query));
|
||||
};
|
||||
var schema;
|
||||
return this.loadSchema(acceptor).then((s) => {
|
||||
let isMaster = !('acl' in options);
|
||||
let aclGroup = options.acl || [];
|
||||
let acceptor = schema => schema.hasKeys(className, keysForQuery(query))
|
||||
let schema = null;
|
||||
return this.loadSchema(acceptor).then(s => {
|
||||
schema = s;
|
||||
if (options.sort) {
|
||||
mongoOptions.sort = {};
|
||||
for (var key in options.sort) {
|
||||
var mongoKey = transform.transformKey(schema, className, key);
|
||||
for (let key in options.sort) {
|
||||
let mongoKey = transform.transformKey(schema, className, key);
|
||||
mongoOptions.sort[mongoKey] = options.sort[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMaster) {
|
||||
var op = 'find';
|
||||
var k = Object.keys(query);
|
||||
if (k.length == 1 && typeof query.objectId == 'string') {
|
||||
op = 'get';
|
||||
}
|
||||
let op = typeof query.objectId == 'string' && Object.keys(query).length === 1 ?
|
||||
'get' :
|
||||
'find';
|
||||
return schema.validatePermission(className, aclGroup, op);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}).then(() => {
|
||||
return this.reduceRelationKeys(className, query);
|
||||
}).then(() => {
|
||||
return this.reduceInRelation(className, query, schema);
|
||||
}).then(() => {
|
||||
return this.adaptiveCollection(className);
|
||||
}).then(collection => {
|
||||
var mongoWhere = transform.transformWhere(schema, className, query);
|
||||
})
|
||||
.then(() => this.reduceRelationKeys(className, query))
|
||||
.then(() => this.reduceInRelation(className, query, schema))
|
||||
.then(() => this.adaptiveCollection(className))
|
||||
.then(collection => {
|
||||
let mongoWhere = transform.transformWhere(schema, className, query);
|
||||
if (!isMaster) {
|
||||
var orParts = [
|
||||
let orParts = [
|
||||
{"_rperm" : { "$exists": false }},
|
||||
{"_rperm" : { "$in" : ["*"]}}
|
||||
];
|
||||
for (var acl of aclGroup) {
|
||||
for (let acl of aclGroup) {
|
||||
orParts.push({"_rperm" : { "$in" : [acl]}});
|
||||
}
|
||||
mongoWhere = {'$and': [mongoWhere, {'$or': orParts}]};
|
||||
|
||||
@@ -52,7 +52,6 @@ export class PushController extends AdaptableController {
|
||||
let badgeUpdate = () => {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (body.data && body.data.badge) {
|
||||
let badge = body.data.badge;
|
||||
let op = {};
|
||||
@@ -89,10 +88,16 @@ export class PushController extends AdaptableController {
|
||||
}).then(() => {
|
||||
return rest.find(config, auth, '_Installation', where);
|
||||
}).then((response) => {
|
||||
pushStatus.setRunning();
|
||||
if (!response.results) {
|
||||
return Promise.reject({error: 'PushController: no results in query'})
|
||||
}
|
||||
pushStatus.setRunning(response.results);
|
||||
return this.sendToAdapter(body, response.results, pushStatus, config);
|
||||
}).then((results) => {
|
||||
return pushStatus.complete(results);
|
||||
}).catch((err) => {
|
||||
pushStatus.fail(err);
|
||||
return Promise.reject(err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -206,7 +206,14 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
}
|
||||
break;
|
||||
case '$exists':
|
||||
if (typeof object[key] === 'undefined') {
|
||||
let propertyExists = typeof object[key] !== 'undefined';
|
||||
let existenceIsRequired = constraints['$exists'];
|
||||
if (typeof constraints['$exists'] !== 'boolean') {
|
||||
// The SDK will never submit a non-boolean for $exists, but if someone
|
||||
// tries to submit a non-boolean for $exits outside the SDKs, just ignore it.
|
||||
break;
|
||||
}
|
||||
if ((!propertyExists && existenceIsRequired) || (propertyExists && !existenceIsRequired)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
+7
-3
@@ -9,6 +9,7 @@ var batch = require('./batch'),
|
||||
middlewares = require('./middlewares'),
|
||||
multer = require('multer'),
|
||||
Parse = require('parse/node').Parse,
|
||||
path = require('path'),
|
||||
authDataManager = require('./authDataManager');
|
||||
|
||||
import { logger,
|
||||
@@ -75,6 +76,7 @@ addParseCloud();
|
||||
// "restAPIKey": optional key from Parse dashboard
|
||||
// "javascriptKey": optional key from Parse dashboard
|
||||
// "push": optional key from configure push
|
||||
// "sessionLength": optional length in seconds for how long Sessions should be valid for
|
||||
|
||||
class ParseServer {
|
||||
|
||||
@@ -111,7 +113,8 @@ class ParseServer {
|
||||
choosePassword: undefined,
|
||||
passwordResetSuccess: undefined
|
||||
},
|
||||
liveQuery = {}
|
||||
liveQuery = {},
|
||||
sessionLength = 31536000, // 1 Year in seconds
|
||||
}) {
|
||||
// Initialize the node client SDK automatically
|
||||
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
|
||||
@@ -140,7 +143,7 @@ class ParseServer {
|
||||
if (typeof cloud === 'function') {
|
||||
cloud(Parse)
|
||||
} else if (typeof cloud === 'string') {
|
||||
require(cloud);
|
||||
require(path.resolve(process.cwd(), cloud));
|
||||
} else {
|
||||
throw "argument 'cloud' must either be a string or a function";
|
||||
}
|
||||
@@ -185,7 +188,8 @@ class ParseServer {
|
||||
publicServerURL: publicServerURL,
|
||||
customPages: customPages,
|
||||
maxUploadSize: maxUploadSize,
|
||||
liveQueryController: liveQueryController
|
||||
liveQueryController: liveQueryController,
|
||||
sessionLength : Number(sessionLength),
|
||||
});
|
||||
|
||||
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability
|
||||
|
||||
+28
-24
@@ -449,39 +449,42 @@ function includePath(config, auth, response, path) {
|
||||
if (pointers.length == 0) {
|
||||
return response;
|
||||
}
|
||||
let pointersHash = {};
|
||||
var className = null;
|
||||
var objectIds = {};
|
||||
for (var pointer of pointers) {
|
||||
if (className === null) {
|
||||
className = pointer.className;
|
||||
} else {
|
||||
if (className != pointer.className) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
||||
'inconsistent type data for include');
|
||||
}
|
||||
let className = pointer.className;
|
||||
// only include the good pointers
|
||||
if (className) {
|
||||
pointersHash[className] = pointersHash[className] || [];
|
||||
pointersHash[className].push(pointer.objectId);
|
||||
}
|
||||
objectIds[pointer.objectId] = true;
|
||||
}
|
||||
if (!className) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_JSON,
|
||||
'bad pointers');
|
||||
}
|
||||
|
||||
let queryPromises = Object.keys(pointersHash).map((className) => {
|
||||
var where = {'objectId': {'$in': pointersHash[className]}};
|
||||
var query = new RestQuery(config, auth, className, where);
|
||||
return query.execute().then((results) => {
|
||||
results.className = className;
|
||||
return Promise.resolve(results);
|
||||
})
|
||||
})
|
||||
|
||||
// Get the objects for all these object ids
|
||||
var where = {'objectId': {'$in': Object.keys(objectIds)}};
|
||||
var query = new RestQuery(config, auth, className, where);
|
||||
return query.execute().then((includeResponse) => {
|
||||
var replace = {};
|
||||
for (var obj of includeResponse.results) {
|
||||
obj.__type = 'Object';
|
||||
obj.className = className;
|
||||
return Promise.all(queryPromises).then((responses) => {
|
||||
var replace = responses.reduce((replace, includeResponse) => {
|
||||
for (var obj of includeResponse.results) {
|
||||
obj.__type = 'Object';
|
||||
obj.className = includeResponse.className;
|
||||
|
||||
if(className == "_User"){
|
||||
delete obj.sessionToken;
|
||||
if(className == "_User"){
|
||||
delete obj.sessionToken;
|
||||
}
|
||||
replace[obj.objectId] = obj;
|
||||
}
|
||||
return replace;
|
||||
}, {})
|
||||
|
||||
replace[obj.objectId] = obj;
|
||||
}
|
||||
var resp = {
|
||||
results: replacePointers(response.results, path, replace)
|
||||
};
|
||||
@@ -534,7 +537,8 @@ function findPointers(object, path) {
|
||||
// pointers inflated.
|
||||
function replacePointers(object, path, replace) {
|
||||
if (object instanceof Array) {
|
||||
return object.map((obj) => replacePointers(obj, path, replace));
|
||||
return object.map((obj) => replacePointers(obj, path, replace))
|
||||
.filter((obj) => obj != null && obj != undefined);
|
||||
}
|
||||
|
||||
if (typeof object !== 'object') {
|
||||
|
||||
+5
-5
@@ -169,6 +169,7 @@ RestWrite.prototype.runBeforeTrigger = function() {
|
||||
if (this.query && this.query.objectId) {
|
||||
delete this.data.objectId
|
||||
}
|
||||
return this.validateSchema();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -302,7 +303,7 @@ RestWrite.prototype.handleAuthData = function(authData) {
|
||||
'this auth is already used');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
@@ -319,8 +320,7 @@ RestWrite.prototype.transformUser = function() {
|
||||
var token = 'r:' + cryptoUtils.newToken();
|
||||
this.storage['token'] = token;
|
||||
promise = promise.then(() => {
|
||||
var expiresAt = new Date();
|
||||
expiresAt.setFullYear(expiresAt.getFullYear() + 1);
|
||||
var expiresAt = this.config.generateSessionExpiresAt();
|
||||
var sessionData = {
|
||||
sessionToken: token,
|
||||
user: {
|
||||
@@ -474,8 +474,7 @@ RestWrite.prototype.handleSession = function() {
|
||||
|
||||
if (!this.query && !this.auth.isMaster) {
|
||||
var token = 'r:' + cryptoUtils.newToken();
|
||||
var expiresAt = new Date();
|
||||
expiresAt.setFullYear(expiresAt.getFullYear() + 1);
|
||||
var expiresAt = this.config.generateSessionExpiresAt();
|
||||
var sessionData = {
|
||||
sessionToken: token,
|
||||
user: {
|
||||
@@ -739,6 +738,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
|
||||
ACL['*'] = { read: true, write: false };
|
||||
this.data.ACL = ACL;
|
||||
}
|
||||
|
||||
// Run a create
|
||||
return this.config.database.create(this.className, this.data, this.runOptions)
|
||||
.then((resp) => {
|
||||
|
||||
@@ -4,6 +4,8 @@ import rest from '../rest';
|
||||
|
||||
import url from 'url';
|
||||
|
||||
const ALLOWED_GET_QUERY_KEYS = ['keys', 'include'];
|
||||
|
||||
export class ClassesRouter extends PromiseRouter {
|
||||
|
||||
handleFind(req) {
|
||||
@@ -59,7 +61,23 @@ export class ClassesRouter extends PromiseRouter {
|
||||
|
||||
// Returns a promise for a {response} object.
|
||||
handleGet(req) {
|
||||
return rest.find(req.config, req.auth, req.params.className, {objectId: req.params.objectId})
|
||||
let body = Object.assign(req.body, ClassesRouter.JSONFromQuery(req.query));
|
||||
let options = {};
|
||||
|
||||
for (let key of Object.keys(body)) {
|
||||
if (ALLOWED_GET_QUERY_KEYS.indexOf(key) === -1) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Improper encode of parameter');
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof body.keys == 'string') {
|
||||
options.keys = body.keys;
|
||||
}
|
||||
if (body.include) {
|
||||
options.include = String(body.include);
|
||||
}
|
||||
|
||||
return rest.find(req.config, req.auth, req.params.className, {objectId: req.params.objectId}, options)
|
||||
.then((response) => {
|
||||
if (!response.results || response.results.length == 0) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import PromiseRouter from '../PromiseRouter';
|
||||
import * as middleware from "../middlewares";
|
||||
import { Parse } from "parse/node";
|
||||
import { Parse } from "parse/node";
|
||||
|
||||
export class PushRouter extends PromiseRouter {
|
||||
|
||||
@@ -46,8 +46,7 @@ export class PushRouter extends PromiseRouter {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
|
||||
'Channels and query should be set at least one.');
|
||||
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, 'Sending a push requires either "channels" or a "where" query.');
|
||||
}
|
||||
return where;
|
||||
}
|
||||
|
||||
@@ -108,9 +108,7 @@ export class UsersRouter extends ClassesRouter {
|
||||
|
||||
req.config.filesController.expandFilesInObject(req.config, user);
|
||||
|
||||
let expiresAt = new Date();
|
||||
expiresAt.setFullYear(expiresAt.getFullYear() + 1);
|
||||
|
||||
let expiresAt = req.config.generateSessionExpiresAt();
|
||||
let sessionData = {
|
||||
sessionToken: token,
|
||||
user: {
|
||||
|
||||
@@ -263,6 +263,16 @@ class Schema {
|
||||
}
|
||||
});
|
||||
if (className) {
|
||||
// merge with the default schema
|
||||
let defaultClassData = Object.assign({}, defaultColumns._Default, defaultColumns[className]);
|
||||
defaultClassData = Object.keys(defaultClassData).reduce((memo, key) => {
|
||||
let type = schemaAPITypeToMongoFieldType(defaultClassData[key]).result;
|
||||
if (type) {
|
||||
memo[key] = type;
|
||||
}
|
||||
return memo;
|
||||
}, {});
|
||||
classData = Object.assign({}, defaultClassData, classData);
|
||||
this.data[className] = classData;
|
||||
if (permsData) {
|
||||
this.perms[className] = permsData;
|
||||
|
||||
+103
-49
@@ -1,10 +1,44 @@
|
||||
function numberParser(key) {
|
||||
return function(opt) {
|
||||
opt = parseInt(opt);
|
||||
if (!Number.isInteger(opt)) {
|
||||
throw new Error(`The ${key} is invalid`);
|
||||
}
|
||||
return opt;
|
||||
}
|
||||
}
|
||||
|
||||
function objectParser(opt) {
|
||||
if (typeof opt == 'object') {
|
||||
return opt;
|
||||
}
|
||||
return JSON.parse(opt)
|
||||
}
|
||||
|
||||
function moduleOrObjectParser(opt) {
|
||||
if (typeof opt == 'object') {
|
||||
return opt;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(opt);
|
||||
} catch(e) {}
|
||||
return opt;
|
||||
}
|
||||
|
||||
function booleanParser(opt) {
|
||||
if (opt == true || opt == "true" || opt == "1") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export default {
|
||||
"appId": {
|
||||
"appId": {
|
||||
env: "PARSE_SERVER_APPLICATION_ID",
|
||||
help: "Your Parse Application ID",
|
||||
required: true
|
||||
},
|
||||
"masterKey": {
|
||||
"masterKey": {
|
||||
env: "PARSE_SERVER_MASTER_KEY",
|
||||
help: "Your Parse Master Key",
|
||||
required: true
|
||||
@@ -13,61 +47,64 @@ export default {
|
||||
env: "PORT",
|
||||
help: "The port to run the ParseServer. defaults to 1337.",
|
||||
default: 1337,
|
||||
action: function(opt) {
|
||||
opt = parseInt(opt);
|
||||
if (!Number.isInteger(opt)) {
|
||||
throw new Error("The port is invalid");
|
||||
}
|
||||
return opt;
|
||||
}
|
||||
action: numberParser("port")
|
||||
},
|
||||
"databaseURI": {
|
||||
"databaseURI": {
|
||||
env: "PARSE_SERVER_DATABASE_URI",
|
||||
help: "The full URI to your mongodb database"
|
||||
},
|
||||
"serverURL": {
|
||||
"databaseOptions": {
|
||||
env: "PARSE_SERVER_DATABASE_OPTIONS",
|
||||
help: "Options to pass to the mongodb client",
|
||||
action: objectParser
|
||||
},
|
||||
"collectionPrefix": {
|
||||
env: "PARSE_SERVER_COLLECTION_PREFIX",
|
||||
help: 'A collection prefix for the classes'
|
||||
},
|
||||
"serverURL": {
|
||||
env: "PARSE_SERVER_URL",
|
||||
help: "URL to your parse server with http:// or https://.",
|
||||
},
|
||||
"clientKey": {
|
||||
"publicServerURL": {
|
||||
env: "PARSE_PUBLIC_SERVER_URL",
|
||||
help: "Public URL to your parse server with http:// or https://.",
|
||||
},
|
||||
"clientKey": {
|
||||
env: "PARSE_SERVER_CLIENT_KEY",
|
||||
help: "Key for iOS, MacOS, tvOS clients"
|
||||
},
|
||||
"javascriptKey": {
|
||||
"javascriptKey": {
|
||||
env: "PARSE_SERVER_JAVASCRIPT_KEY",
|
||||
help: "Key for the Javascript SDK"
|
||||
},
|
||||
"restAPIKey": {
|
||||
},
|
||||
"restAPIKey": {
|
||||
env: "PARSE_SERVER_REST_API_KEY",
|
||||
help: "Key for REST calls"
|
||||
},
|
||||
"dotNetKey": {
|
||||
},
|
||||
"dotNetKey": {
|
||||
env: "PARSE_SERVER_DOT_NET_KEY",
|
||||
help: "Key for Unity and .Net SDK"
|
||||
},
|
||||
"cloud": {
|
||||
},
|
||||
"cloud": {
|
||||
env: "PARSE_SERVER_CLOUD_CODE_MAIN",
|
||||
help: "Full path to your cloud code main.js"
|
||||
},
|
||||
},
|
||||
"push": {
|
||||
env: "PARSE_SERVER_PUSH",
|
||||
help: "Configuration for push, as stringified JSON. See https://github.com/ParsePlatform/parse-server/wiki/Push",
|
||||
action: function(opt) {
|
||||
return JSON.parse(opt)
|
||||
}
|
||||
action: objectParser
|
||||
},
|
||||
"oauth": {
|
||||
"oauth": {
|
||||
env: "PARSE_SERVER_OAUTH_PROVIDERS",
|
||||
help: "Configuration for your oAuth providers, as stringified JSON. See https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#oauth",
|
||||
action: function(opt) {
|
||||
return JSON.parse(opt)
|
||||
}
|
||||
action: objectParser
|
||||
},
|
||||
"fileKey": {
|
||||
"fileKey": {
|
||||
env: "PARSE_SERVER_FILE_KEY",
|
||||
help: "Key for your files",
|
||||
},
|
||||
"facebookAppIds": {
|
||||
},
|
||||
"facebookAppIds": {
|
||||
env: "PARSE_SERVER_FACEBOOK_APP_IDS",
|
||||
help: "Comma separated list for your facebook app Ids",
|
||||
type: "list",
|
||||
@@ -78,43 +115,60 @@ export default {
|
||||
"enableAnonymousUsers": {
|
||||
env: "PARSE_SERVER_ENABLE_ANON_USERS",
|
||||
help: "Enable (or disable) anon users, defaults to true",
|
||||
action: function(opt) {
|
||||
if (opt == "true" || opt == "1") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
action: booleanParser
|
||||
},
|
||||
"allowClientClassCreation": {
|
||||
env: "PARSE_SERVER_ALLOW_CLIENT_CLASS_CREATION",
|
||||
help: "Enable (or disable) client class creation, defaults to true",
|
||||
action: function(opt) {
|
||||
if (opt == "true" || opt == "1") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
action: booleanParser
|
||||
},
|
||||
"mountPath": {
|
||||
"mountPath": {
|
||||
env: "PARSE_SERVER_MOUNT_PATH",
|
||||
help: "Mount path for the server, defaults to /parse",
|
||||
default: "/parse"
|
||||
},
|
||||
"databaseAdapter": {
|
||||
env: "PARSE_SERVER_DATABASE_ADAPTER",
|
||||
help: "Adapter module for the database sub-system"
|
||||
},
|
||||
"filesAdapter": {
|
||||
env: "PARSE_SERVER_FILES_ADAPTER",
|
||||
help: "Adapter module for the files sub-system"
|
||||
help: "Adapter module for the files sub-system",
|
||||
action: moduleOrObjectParser
|
||||
},
|
||||
"emailAdapter": {
|
||||
env: "PARSE_SERVER_EMAIL_ADAPTER",
|
||||
help: "Adapter module for the email sending",
|
||||
action: moduleOrObjectParser
|
||||
},
|
||||
"verifyUserEmails": {
|
||||
env: "PARSE_SERVER_VERIFY_USER_EMAILS",
|
||||
help: "Enable (or disable) user email validation, defaults to false",
|
||||
action: booleanParser
|
||||
},
|
||||
"appName": {
|
||||
env: "PARSE_SERVER_APP_NAME",
|
||||
help: "Sets the app name"
|
||||
},
|
||||
"loggerAdapter": {
|
||||
env: "PARSE_SERVER_LOGGER_ADAPTER",
|
||||
help: "Adapter module for the logging sub-system"
|
||||
help: "Adapter module for the logging sub-system",
|
||||
action: moduleOrObjectParser
|
||||
},
|
||||
"liveQuery": {
|
||||
env: "PARSE_SERVER_LIVE_QUERY_OPTIONS",
|
||||
help: "liveQuery options",
|
||||
action: objectParser
|
||||
},
|
||||
"customPages": {
|
||||
env: "PARSE_SERVER_CUSTOM_PAGES",
|
||||
help: "custom pages for pasword validation and reset",
|
||||
action: objectParser
|
||||
},
|
||||
"maxUploadSize": {
|
||||
env: "PARSE_SERVER_MAX_UPLOAD_SIZE",
|
||||
help: "Max file size for uploads.",
|
||||
default: "20mb"
|
||||
},
|
||||
"sessionLength": {
|
||||
env: "PARSE_SERVER_SESSION_LENGTH",
|
||||
help: "Session duration, defaults to 1 year",
|
||||
action: numberParser("sessionLength")
|
||||
}
|
||||
};
|
||||
|
||||
+2
-22
@@ -3,6 +3,7 @@ import express from 'express';
|
||||
import { ParseServer } from '../index';
|
||||
import definitions from './cli-definitions';
|
||||
import program from './utils/commander';
|
||||
import { mergeWithOptions } from './utils/commander';
|
||||
import colors from 'colors';
|
||||
|
||||
program.loadDefinitions(definitions);
|
||||
@@ -34,28 +35,7 @@ program.on('--help', function(){
|
||||
|
||||
program.parse(process.argv, process.env);
|
||||
|
||||
let options = {};
|
||||
if (program.args.length > 0 ) {
|
||||
let jsonPath = program.args[0];
|
||||
jsonPath = path.resolve(jsonPath);
|
||||
let jsonConfig = require(jsonPath);
|
||||
if (jsonConfig.apps) {
|
||||
if (jsonConfig.apps.length > 1) {
|
||||
throw 'Multiple apps are not supported';
|
||||
}
|
||||
options = jsonConfig.apps[0];
|
||||
} else {
|
||||
options = jsonConfig;
|
||||
}
|
||||
console.log(`Configuation loaded from ${jsonPath}`)
|
||||
}
|
||||
|
||||
options = Object.keys(definitions).reduce(function (options, key) {
|
||||
if (typeof program[key] !== 'undefined') {
|
||||
options[key] = program[key];
|
||||
}
|
||||
return options;
|
||||
}, options);
|
||||
let options = program.getOptions();
|
||||
|
||||
if (!options.serverURL) {
|
||||
options.serverURL = `http://localhost:${options.port}${options.mountPath}`;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Command } from 'commander';
|
||||
|
||||
import path from 'path';
|
||||
let _definitions;
|
||||
let _reverseDefinitions;
|
||||
let _defaults;
|
||||
|
||||
Command.prototype.loadDefinitions = function(definitions) {
|
||||
_definitions = definitions;
|
||||
|
||||
|
||||
Object.keys(definitions).reduce((program, opt) => {
|
||||
if (typeof definitions[opt] == "object") {
|
||||
const additionalOptions = definitions[opt];
|
||||
@@ -18,14 +18,14 @@ Command.prototype.loadDefinitions = function(definitions) {
|
||||
}
|
||||
return program.option(`--${opt} [${opt}]`);
|
||||
}, this);
|
||||
|
||||
|
||||
_defaults = Object.keys(definitions).reduce((defs, opt) => {
|
||||
if(_definitions[opt].default) {
|
||||
defs[opt] = _definitions[opt].default;
|
||||
}
|
||||
return defs;
|
||||
}, {});
|
||||
|
||||
|
||||
_reverseDefinitions = Object.keys(definitions).reduce((object, key) => {
|
||||
let value = definitions[key];
|
||||
if (typeof value == "object") {
|
||||
@@ -36,7 +36,7 @@ Command.prototype.loadDefinitions = function(definitions) {
|
||||
}
|
||||
return object;
|
||||
}, {});
|
||||
|
||||
|
||||
/* istanbul ignore next */
|
||||
this.on('--help', function(){
|
||||
console.log(' Configure From Environment:');
|
||||
@@ -58,17 +58,46 @@ function parseEnvironment(env = {}) {
|
||||
}
|
||||
options[_reverseDefinitions[key]] = action(env[key]);
|
||||
}
|
||||
return options;
|
||||
return options;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function parseConfigFile(program) {
|
||||
let options = {};
|
||||
if (program.args.length > 0) {
|
||||
let jsonPath = program.args[0];
|
||||
jsonPath = path.resolve(jsonPath);
|
||||
let jsonConfig = require(jsonPath);
|
||||
if (jsonConfig.apps) {
|
||||
if (jsonConfig.apps.length > 1) {
|
||||
throw 'Multiple apps are not supported';
|
||||
}
|
||||
options = jsonConfig.apps[0];
|
||||
} else {
|
||||
options = jsonConfig;
|
||||
}
|
||||
Object.keys(options).forEach((key) => {
|
||||
let value = options[key];
|
||||
if (!_definitions[key]) {
|
||||
throw `error: unknown option ${key}`;
|
||||
}
|
||||
let action = _definitions[key].action;
|
||||
if (action) {
|
||||
options[key] = action(value);
|
||||
}
|
||||
})
|
||||
console.log(`Configuation loaded from ${jsonPath}`)
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
Command.prototype.setValuesIfNeeded = function(options) {
|
||||
Object.keys(options).forEach((key) => {
|
||||
if (!this[key]) {
|
||||
this[key] = options[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Command.prototype._parse = Command.prototype.parse;
|
||||
|
||||
@@ -76,10 +105,22 @@ Command.prototype.parse = function(args, env) {
|
||||
this._parse(args);
|
||||
// Parse the environment first
|
||||
const envOptions = parseEnvironment(env);
|
||||
|
||||
const fromFile = parseConfigFile(this);
|
||||
// Load the env if not passed from command line
|
||||
this.setValuesIfNeeded(envOptions);
|
||||
// Load from file to override
|
||||
this.setValuesIfNeeded(fromFile);
|
||||
// Last set the defaults
|
||||
this.setValuesIfNeeded(_defaults);
|
||||
}
|
||||
|
||||
Command.prototype.getOptions = function() {
|
||||
return Object.keys(_definitions).reduce((options, key) => {
|
||||
if (typeof this[key] !== 'undefined') {
|
||||
options[key] = this[key];
|
||||
}
|
||||
return options;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export default new Command();
|
||||
|
||||
+36
-46
@@ -10,7 +10,31 @@ if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
|
||||
}
|
||||
|
||||
let currentLogsFolder = LOGS_FOLDER;
|
||||
var currentTransports;
|
||||
|
||||
function generateTransports() {
|
||||
let level = process.env.VERBOSE ? 'verbose': 'info';
|
||||
let transports = [
|
||||
new (DailyRotateFile)({
|
||||
filename: 'parse-server.info',
|
||||
dirname: currentLogsFolder,
|
||||
name: 'parse-server',
|
||||
level: level
|
||||
}),
|
||||
new (DailyRotateFile)({
|
||||
filename: 'parse-server.err',
|
||||
dirname: currentLogsFolder,
|
||||
name: 'parse-server-error',
|
||||
level: 'error'
|
||||
})
|
||||
]
|
||||
if (!process.env.TESTING || process.env.VERBOSE) {
|
||||
transports = [new (winston.transports.Console)({
|
||||
colorize: true,
|
||||
level:level
|
||||
})].concat(transports);
|
||||
}
|
||||
return transports;
|
||||
}
|
||||
|
||||
const logger = new winston.Logger();
|
||||
|
||||
@@ -25,27 +49,8 @@ export function configureLogger({logsFolder}) {
|
||||
}
|
||||
currentLogsFolder = logsFolder;
|
||||
|
||||
currentTransports = [
|
||||
new (winston.transports.Console)({
|
||||
colorize: true,
|
||||
level: process.env.VERBOSE ? 'verbose': 'info'
|
||||
}),
|
||||
new (DailyRotateFile)({
|
||||
filename: 'parse-server.info',
|
||||
dirname: currentLogsFolder,
|
||||
name: 'parse-server',
|
||||
level: process.env.VERBOSE ? 'verbose': 'info'
|
||||
}),
|
||||
new (DailyRotateFile)({
|
||||
filename: 'parse-server.err',
|
||||
dirname: currentLogsFolder,
|
||||
name: 'parse-server-error',
|
||||
level: 'error'
|
||||
})
|
||||
]
|
||||
|
||||
logger.configure({
|
||||
transports: currentTransports
|
||||
transports: generateTransports()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -53,34 +58,19 @@ configureLogger({logsFolder: LOGS_FOLDER});
|
||||
|
||||
export function addGroup(groupName) {
|
||||
let level = process.env.VERBOSE ? 'verbose': 'info';
|
||||
|
||||
let transports = generateTransports().concat(new (DailyRotateFile)({
|
||||
filename: groupName,
|
||||
dirname: currentLogsFolder,
|
||||
name: groupName,
|
||||
level: level
|
||||
}));
|
||||
|
||||
winston.loggers.add(groupName, {
|
||||
transports: [
|
||||
new (winston.transports.Console)({
|
||||
colorize: true,
|
||||
level: level
|
||||
}),
|
||||
new (DailyRotateFile)({
|
||||
filename: groupName,
|
||||
dirname: currentLogsFolder,
|
||||
name: groupName,
|
||||
level: level
|
||||
}),
|
||||
new (DailyRotateFile)({
|
||||
filename: 'parse-server.info',
|
||||
name: 'parse-server',
|
||||
dirname: currentLogsFolder,
|
||||
level: level
|
||||
}),
|
||||
new (DailyRotateFile)({
|
||||
filename: 'parse-server.err',
|
||||
dirname: currentLogsFolder,
|
||||
name: 'parse-server-error',
|
||||
level: 'error'
|
||||
})
|
||||
]
|
||||
transports: transports
|
||||
});
|
||||
return winston.loggers.get(groupName);
|
||||
}
|
||||
|
||||
export { logger };
|
||||
export default winston;
|
||||
export default logger;
|
||||
|
||||
+9
-3
@@ -128,9 +128,15 @@ function handleParseHeaders(req, res, next) {
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// TODO: Determine the correct error scenario.
|
||||
log.error('error getting auth for sessionToken', error);
|
||||
throw new Parse.Error(Parse.Error.UNKNOWN_ERROR, error);
|
||||
if(error instanceof Parse.Error) {
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// TODO: Determine the correct error scenario.
|
||||
log.error('error getting auth for sessionToken', error);
|
||||
throw new Parse.Error(Parse.Error.UNKNOWN_ERROR, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { md5Hash, newObjectId } from './cryptoUtils';
|
||||
import { logger } from './logger';
|
||||
|
||||
export function flatten(array) {
|
||||
return array.reduce((memo, element) => {
|
||||
@@ -20,8 +21,9 @@ export default function pushStatusHandler(config) {
|
||||
return config.database.adaptiveCollection('_PushStatus');
|
||||
}
|
||||
|
||||
let setInitial = function(body, where, options = {source: 'rest'}) {
|
||||
let setInitial = function(body = {}, where, options = {source: 'rest'}) {
|
||||
let now = new Date();
|
||||
let data = body.data || {};
|
||||
let object = {
|
||||
objectId: newObjectId(),
|
||||
pushTime: now.toISOString(),
|
||||
@@ -33,7 +35,7 @@ export default function pushStatusHandler(config) {
|
||||
expiry: body.expiration_time,
|
||||
status: "pending",
|
||||
numSent: 0,
|
||||
pushHash: md5Hash(JSON.stringify(body.data)),
|
||||
pushHash: md5Hash(JSON.stringify(data)),
|
||||
// lockdown!
|
||||
_wperm: [],
|
||||
_rperm: []
|
||||
@@ -49,7 +51,8 @@ export default function pushStatusHandler(config) {
|
||||
return initialPromise;
|
||||
}
|
||||
|
||||
let setRunning = function() {
|
||||
let setRunning = function(installations) {
|
||||
logger.verbose('sending push to %d installations', installations.length);
|
||||
return initialPromise.then(() => {
|
||||
return collection();
|
||||
}).then((collection) => {
|
||||
@@ -86,7 +89,7 @@ export default function pushStatusHandler(config) {
|
||||
return memo;
|
||||
}, update);
|
||||
}
|
||||
|
||||
logger.verbose('sent push! %d success, %d failures', update.numSent, update.numFailed);
|
||||
return initialPromise.then(() => {
|
||||
return collection();
|
||||
}).then((collection) => {
|
||||
@@ -94,9 +97,23 @@ export default function pushStatusHandler(config) {
|
||||
});
|
||||
}
|
||||
|
||||
let fail = function(err) {
|
||||
let update = {
|
||||
errorMessage: JSON.stringify(err),
|
||||
status: 'failed'
|
||||
}
|
||||
logger.error('error while sending push', err);
|
||||
return initialPromise.then(() => {
|
||||
return collection();
|
||||
}).then((collection) => {
|
||||
return collection.updateOne({objectId: pushStatus.objectId}, {$set: update});
|
||||
});
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
setInitial,
|
||||
setRunning,
|
||||
complete
|
||||
complete,
|
||||
fail
|
||||
})
|
||||
}
|
||||
|
||||
+5
-5
@@ -119,11 +119,11 @@ function update(config, auth, className, objectId, restObject) {
|
||||
|
||||
// Disallowing access to the _Role collection except by master key
|
||||
function enforceRoleSecurity(method, className, auth) {
|
||||
if (method === 'delete' && className === '_Installation' && !auth.isMaster) {
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN,
|
||||
'Clients aren\'t allowed to perform the ' +
|
||||
'delete operation on the installation collection.');
|
||||
|
||||
if (className === '_Installation' && !auth.isMaster) {
|
||||
if (method === 'delete' || method === 'find') {
|
||||
let error = `Clients aren't allowed to perform the ${method} operation on the installation collection.`
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-5
@@ -187,13 +187,12 @@ export function transformKeyValue(schema, className, restKey, restValue, options
|
||||
// Returns the mongo form of the query.
|
||||
// Throws a Parse.Error if the input query is invalid.
|
||||
function transformWhere(schema, className, restWhere) {
|
||||
var mongoWhere = {};
|
||||
let mongoWhere = {};
|
||||
if (restWhere['ACL']) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY,
|
||||
'Cannot query on ACL.');
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot query on ACL.');
|
||||
}
|
||||
for (var restKey in restWhere) {
|
||||
var out = transformKeyValue(schema, className, restKey, restWhere[restKey],
|
||||
for (let restKey in restWhere) {
|
||||
let out = transformKeyValue(schema, className, restKey, restWhere[restKey],
|
||||
{query: true, validate: true});
|
||||
mongoWhere[out.key] = out.value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user