Skip to content

Commit 401e584

Browse files
committed
Merge remote-tracking branch 'upstream/master' into add-idempotency
2 parents 6c03028 + e848b54 commit 401e584

File tree

14 files changed

+1619
-2653
lines changed

14 files changed

+1619
-2653
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232

3333
</p>
3434
<p align="center">
35-
<a href="#backers"><img alt="Backers on Open Collective" src="https://opencollective.com/parse-server/backers/badge.svg" /></a>
36-
<a href="#sponsors"><img alt="Sponsors on Open Collective" src="https://opencollective.com/parse-server/sponsors/badge.svg" /></a>
35+
<a href="#backers"><img alt="Backers on Open Collective" src="https://opencollective.com/parse-server/tiers/backers/badge.svg" /></a>
36+
<a href="#sponsors"><img alt="Sponsors on Open Collective" src="https://opencollective.com/parse-server/tiers/sponsors/badge.svg" /></a>
3737
</p>
3838
<br>
3939

package-lock.json

Lines changed: 1437 additions & 2621 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,35 +19,35 @@
1919
],
2020
"license": "BSD-3-Clause",
2121
"dependencies": {
22-
"@apollographql/graphql-playground-html": "1.6.24",
23-
"@graphql-tools/stitch": "^6.0.0",
24-
"@graphql-tools/utils": "^6.0.0",
22+
"@apollographql/graphql-playground-html": "1.6.26",
23+
"@graphql-tools/stitch": "^6.0.1",
24+
"@graphql-tools/utils": "^6.0.1",
2525
"@parse/fs-files-adapter": "1.0.1",
2626
"@parse/push-adapter": "3.2.0",
2727
"@parse/s3-files-adapter": "1.4.0",
2828
"@parse/simple-mailgun-adapter": "1.1.0",
29-
"apollo-server-express": "2.14.0",
29+
"apollo-server-express": "2.14.1",
3030
"bcryptjs": "2.4.3",
3131
"body-parser": "1.19.0",
3232
"commander": "5.1.0",
3333
"cors": "2.8.5",
3434
"deepcopy": "2.0.0",
3535
"express": "4.17.1",
3636
"follow-redirects": "1.11.0",
37-
"graphql": "15.0.0",
37+
"graphql": "15.1.0",
3838
"graphql-list-fields": "2.0.2",
3939
"graphql-relay": "^0.6.0",
4040
"graphql-upload": "11.0.0",
4141
"intersect": "1.0.1",
4242
"jsonwebtoken": "8.5.1",
4343
"jwks-rsa": "1.8.0",
44-
"ldapjs": "1.0.2",
45-
"lodash": "4.17.15",
44+
"ldapjs": "2.0.0",
45+
"lodash": "4.17.16",
4646
"lru-cache": "5.1.1",
4747
"mime": "2.4.6",
48-
"mongodb": "3.5.8",
49-
"parse": "2.13.0",
50-
"pg-promise": "10.5.4",
48+
"mongodb": "3.5.9",
49+
"parse": "2.14.0",
50+
"pg-promise": "10.5.6",
5151
"pluralize": "^8.0.0",
5252
"redis": "3.0.2",
5353
"semver": "7.3.2",
@@ -66,7 +66,7 @@
6666
"@babel/preset-env": "7.10.0",
6767
"@parse/minami": "1.0.0",
6868
"apollo-cache-inmemory": "1.6.6",
69-
"apollo-client": "2.6.9",
69+
"apollo-client": "2.6.10",
7070
"apollo-link": "1.2.14",
7171
"apollo-link-http": "1.5.17",
7272
"apollo-link-ws": "1.0.20",
@@ -77,10 +77,9 @@
7777
"cross-env": "7.0.2",
7878
"deep-diff": "1.0.2",
7979
"eslint": "6.8.0",
80-
"eslint-plugin-flowtype": "5.0.3",
80+
"eslint-plugin-flowtype": "5.1.3",
8181
"flow-bin": "0.119.1",
8282
"form-data": "3.0.0",
83-
"gaze": "1.1.3",
8483
"graphql-tag": "^2.10.1",
8584
"husky": "4.2.5",
8685
"jasmine": "3.5.0",
@@ -89,13 +88,12 @@
8988
"lint-staged": "10.2.3",
9089
"mongodb-runner": "4.8.0",
9190
"node-fetch": "2.6.0",
92-
"nyc": "15.0.1",
91+
"nyc": "15.1.0",
9392
"prettier": "2.0.5"
9493
},
9594
"scripts": {
9695
"definitions": "node ./resources/buildConfigDefinitions.js",
9796
"docs": "jsdoc -c ./jsdoc-conf.json",
98-
"dev": "npm run build && node bin/dev",
9997
"lint": "flow && eslint --cache ./",
10098
"lint-fix": "eslint --fix --cache ./",
10199
"build": "babel src/ -d lib/ --copy-files",

spec/CloudCode.spec.js

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2912,16 +2912,60 @@ describe('afterLogin hook', () => {
29122912
done();
29132913
});
29142914

2915-
it('should have access to context as save argument', async () => {
2916-
// Declare triggers
2915+
it('should have access to context when saving a new object', async () => {
29172916
Parse.Cloud.beforeSave('TestObject', (req) => {
29182917
expect(req.context.a).toEqual('a');
29192918
});
29202919
Parse.Cloud.afterSave('TestObject', (req) => {
29212920
expect(req.context.a).toEqual('a');
29222921
});
2923-
// Save object
29242922
const obj = new TestObject();
29252923
await obj.save(null, { context: { a: 'a' } });
29262924
});
2925+
2926+
it('should have access to context when saving an existing object', async () => {
2927+
const obj = new TestObject();
2928+
await obj.save(null);
2929+
Parse.Cloud.beforeSave('TestObject', (req) => {
2930+
expect(req.context.a).toEqual('a');
2931+
});
2932+
Parse.Cloud.afterSave('TestObject', (req) => {
2933+
expect(req.context.a).toEqual('a');
2934+
});
2935+
await obj.save(null, { context: { a: 'a' } });
2936+
});
2937+
2938+
it('should have access to context when saving a new object in a trigger', async () => {
2939+
Parse.Cloud.beforeSave('TestObject', (req) => {
2940+
expect(req.context.a).toEqual('a');
2941+
});
2942+
Parse.Cloud.afterSave('TestObject', (req) => {
2943+
expect(req.context.a).toEqual('a');
2944+
});
2945+
Parse.Cloud.afterSave('TriggerObject', async () => {
2946+
const obj = new TestObject();
2947+
await obj.save(null, { context: { a: 'a' } });
2948+
});
2949+
const obj = new Parse.Object('TriggerObject');
2950+
await obj.save(null);
2951+
});
2952+
2953+
it('should have access to context when cascade-saving objects', async () => {
2954+
Parse.Cloud.beforeSave('TestObject', (req) => {
2955+
expect(req.context.a).toEqual('a');
2956+
});
2957+
Parse.Cloud.afterSave('TestObject', (req) => {
2958+
expect(req.context.a).toEqual('a');
2959+
});
2960+
Parse.Cloud.beforeSave('TestObject2', (req) => {
2961+
expect(req.context.a).toEqual('a');
2962+
});
2963+
Parse.Cloud.afterSave('TestObject2', (req) => {
2964+
expect(req.context.a).toEqual('a');
2965+
});
2966+
const obj = new Parse.Object("TestObject");
2967+
const obj2 = new Parse.Object("TestObject2");
2968+
obj.set("obj2", obj2);
2969+
await obj.save(null, { context: { a: 'a' } });
2970+
});
29272971
});

spec/GridFSBucketStorageAdapter.spec.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ describe_only_db('mongo')('GridFSBucket and GridStore interop', () => {
3535
expect(gfsResult.toString('utf8')).toBe(originalString);
3636
});
3737

38+
it('an encypted file created in GridStore should be available in GridFS', async () => {
39+
const gsAdapter = new GridStoreAdapter(databaseURI);
40+
const gfsAdapter = new GridFSBucketAdapter(
41+
databaseURI,
42+
{},
43+
'89E4AFF1-DFE4-4603-9574-BFA16BB446FD'
44+
);
45+
await expectMissingFile(gfsAdapter, 'myFileName');
46+
const originalString = 'abcdefghi';
47+
await gfsAdapter.createFile('myFileName', originalString);
48+
const gsResult = await gsAdapter.getFileData('myFileName');
49+
expect(gsResult.toString('utf8')).not.toBe(originalString);
50+
const gfsResult = await gfsAdapter.getFileData('myFileName');
51+
expect(gfsResult.toString('utf8')).toBe(originalString);
52+
});
53+
3854
it('should save metadata', async () => {
3955
const gfsAdapter = new GridFSBucketAdapter(databaseURI);
4056
const originalString = 'abcdefghi';

spec/Middlewares.spec.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,42 @@ describe('middlewares', () => {
357357
);
358358
});
359359

360+
it('should set default Access-Control-Allow-Origin if allowOrigin is empty', () => {
361+
AppCache.put(fakeReq.body._ApplicationId, {
362+
allowOrigin: undefined,
363+
});
364+
const headers = {};
365+
const res = {
366+
header: (key, value) => {
367+
headers[key] = value;
368+
},
369+
};
370+
const allowCrossDomain = middlewares.allowCrossDomain(
371+
fakeReq.body._ApplicationId
372+
);
373+
allowCrossDomain(fakeReq, res, () => {});
374+
expect(headers['Access-Control-Allow-Origin']).toEqual('*');
375+
});
376+
377+
it('should set custom origin to Access-Control-Allow-Origin if allowOrigin is provided', () => {
378+
AppCache.put(fakeReq.body._ApplicationId, {
379+
allowOrigin: 'https://parseplatform.org/',
380+
});
381+
const headers = {};
382+
const res = {
383+
header: (key, value) => {
384+
headers[key] = value;
385+
},
386+
};
387+
const allowCrossDomain = middlewares.allowCrossDomain(
388+
fakeReq.body._ApplicationId
389+
);
390+
allowCrossDomain(fakeReq, res, () => {});
391+
expect(headers['Access-Control-Allow-Origin']).toEqual(
392+
'https://parseplatform.org/'
393+
);
394+
});
395+
360396
it('should use user provided on field userFromJWT', (done) => {
361397
AppCache.put(fakeReq.body._ApplicationId, {
362398
masterKey: 'masterKey',

src/Adapters/Files/GridFSBucketAdapter.js

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,30 @@
1010
import { MongoClient, GridFSBucket, Db } from 'mongodb';
1111
import { FilesAdapter, validateFilename } from './FilesAdapter';
1212
import defaults from '../../defaults';
13+
const crypto = require('crypto');
1314

1415
export class GridFSBucketAdapter extends FilesAdapter {
1516
_databaseURI: string;
1617
_connectionPromise: Promise<Db>;
1718
_mongoOptions: Object;
19+
_algorithm: string;
1820

19-
constructor(mongoDatabaseURI = defaults.DefaultMongoURI, mongoOptions = {}) {
21+
constructor(
22+
mongoDatabaseURI = defaults.DefaultMongoURI,
23+
mongoOptions = {},
24+
fileKey = undefined
25+
) {
2026
super();
2127
this._databaseURI = mongoDatabaseURI;
22-
28+
this._algorithm = 'aes-256-gcm';
29+
this._fileKey =
30+
fileKey !== undefined
31+
? crypto
32+
.createHash('sha256')
33+
.update(String(fileKey))
34+
.digest('base64')
35+
.substr(0, 32)
36+
: null;
2337
const defaultMongoOptions = {
2438
useNewUrlParser: true,
2539
useUnifiedTopology: true,
@@ -51,7 +65,19 @@ export class GridFSBucketAdapter extends FilesAdapter {
5165
const stream = await bucket.openUploadStream(filename, {
5266
metadata: options.metadata,
5367
});
54-
await stream.write(data);
68+
if (this._fileKey !== null) {
69+
const iv = crypto.randomBytes(16);
70+
const cipher = crypto.createCipheriv(this._algorithm, this._fileKey, iv);
71+
const encryptedResult = Buffer.concat([
72+
cipher.update(data),
73+
cipher.final(),
74+
iv,
75+
cipher.getAuthTag(),
76+
]);
77+
await stream.write(encryptedResult);
78+
} else {
79+
await stream.write(data);
80+
}
5581
stream.end();
5682
return new Promise((resolve, reject) => {
5783
stream.on('finish', resolve);
@@ -82,7 +108,24 @@ export class GridFSBucketAdapter extends FilesAdapter {
82108
chunks.push(data);
83109
});
84110
stream.on('end', () => {
85-
resolve(Buffer.concat(chunks));
111+
const data = Buffer.concat(chunks);
112+
if (this._fileKey !== null) {
113+
const authTagLocation = data.length - 16;
114+
const ivLocation = data.length - 32;
115+
const authTag = data.slice(authTagLocation);
116+
const iv = data.slice(ivLocation, authTagLocation);
117+
const encrypted = data.slice(0, ivLocation);
118+
const decipher = crypto.createDecipheriv(
119+
this._algorithm,
120+
this._fileKey,
121+
iv
122+
);
123+
decipher.setAuthTag(authTag);
124+
return resolve(
125+
Buffer.concat([decipher.update(encrypted), decipher.final()])
126+
);
127+
}
128+
resolve(data);
86129
});
87130
stream.on('error', (err) => {
88131
reject(err);

src/Controllers/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,13 @@ export function getFilesController(
105105
filesAdapter,
106106
databaseAdapter,
107107
preserveFileName,
108+
fileKey,
108109
} = options;
109110
if (!filesAdapter && databaseAdapter) {
110111
throw 'When using an explicit database adapter, you must also use an explicit filesAdapter.';
111112
}
112113
const filesControllerAdapter = loadAdapter(filesAdapter, () => {
113-
return new GridFSBucketAdapter(databaseURI);
114+
return new GridFSBucketAdapter(databaseURI, {}, fileKey);
114115
});
115116
return new FilesController(filesControllerAdapter, appId, {
116117
preserveFileName,

src/Options/Definitions.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ module.exports.ParseServerOptions = {
2828
"help": "Add headers to Access-Control-Allow-Headers",
2929
"action": parsers.arrayParser
3030
},
31+
"allowOrigin": {
32+
"env": "PARSE_SERVER_ALLOW_ORIGIN",
33+
"help": "Sets the origin to Access-Control-Allow-Origin"
34+
},
3135
"analyticsAdapter": {
3236
"env": "PARSE_SERVER_ANALYTICS_ADAPTER",
3337
"help": "Adapter module for the analytics",

src/Options/docs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* @property {Boolean} allowClientClassCreation Enable (or disable) client class creation, defaults to true
55
* @property {Boolean} allowCustomObjectId Enable (or disable) custom objectId
66
* @property {String[]} allowHeaders Add headers to Access-Control-Allow-Headers
7+
* @property {String} allowOrigin Sets the origin to Access-Control-Allow-Origin
78
* @property {Adapter<AnalyticsAdapter>} analyticsAdapter Adapter module for the analytics
89
* @property {String} appId Your Parse Application ID
910
* @property {String} appName Sets the app name

src/Options/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export interface ParseServerOptions {
2929
appName: ?string;
3030
/* Add headers to Access-Control-Allow-Headers */
3131
allowHeaders: ?(string[]);
32+
/* Sets the origin to Access-Control-Allow-Origin */
33+
allowOrigin: ?string;
3234
/* Adapter module for the analytics */
3335
analyticsAdapter: ?Adapter<AnalyticsAdapter>;
3436
/* Adapter module for the files sub-system */

src/RestWrite.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,13 @@ function RestWrite(
5252
this.runOptions.action = action;
5353
}
5454

55+
// Parse context
56+
if (data._context && data._context instanceof Object) {
57+
this.context = data._context;
58+
delete data._context;
59+
}
60+
5561
if (!query) {
56-
// Parse context
57-
if (data._context && data._context instanceof Object) {
58-
this.context = data._context;
59-
delete data._context;
60-
}
6162
if (this.config.allowCustomObjectId) {
6263
if (
6364
Object.prototype.hasOwnProperty.call(data, 'objectId') &&

src/batch.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,18 @@ function handleBatch(router, req) {
9191
return initialPromise.then(() => {
9292
const promises = req.body.requests.map(restRequest => {
9393
const routablePath = makeRoutablePath(restRequest.path);
94-
// Construct a request that we can send to a handler
9594

95+
// Construct a request that we can send to a handler
9696
const request = {
9797
body: restRequest.body,
9898
config: req.config,
9999
auth: req.auth,
100100
info: req.info,
101101
};
102102

103+
// Add context to request body
104+
if (req.body._context) { request.body._context = req.body._context; }
105+
103106
return router
104107
.tryRouteRequest(restRequest.method, routablePath, request)
105108
.then(

src/middlewares.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,8 @@ export function allowCrossDomain(appId) {
318318
if (config && config.allowHeaders) {
319319
allowHeaders += `, ${config.allowHeaders.join(', ')}`;
320320
}
321-
res.header('Access-Control-Allow-Origin', '*');
321+
const allowOrigin = (config && config.allowOrigin) || '*';
322+
res.header('Access-Control-Allow-Origin', allowOrigin);
322323
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
323324
res.header('Access-Control-Allow-Headers', allowHeaders);
324325
res.header(

0 commit comments

Comments
 (0)