Skip to content

Commit 005c886

Browse files
committed
Merge branch 'master' into master
2 parents 37d145f + b36fd6f commit 005c886

15 files changed

+262
-52
lines changed

.github/ISSUE_TEMPLATE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
Make sure these boxes are checked before submitting your issue -- thanks for reporting issues back to Parse Server!
22

3-
-[ ] You've met the [prerequisites](https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#prerequisites).
3+
- [ ] You've met the [prerequisites](https://github.com/ParsePlatform/parse-server/wiki/Parse-Server-Guide#prerequisites).
44

5-
-[ ] You're running the [latest version](https://github.com/ParsePlatform/parse-server/releases) of Parse Server.
5+
- [ ] You're running the [latest version](https://github.com/ParsePlatform/parse-server/releases) of Parse Server.
66

7-
-[ ] You've searched through [existing issues](https://github.com/ParsePlatform/parse-server/issues?utf8=%E2%9C%93&q=). Chances are that your issue has been reported or resolved before.
7+
- [ ] You've searched through [existing issues](https://github.com/ParsePlatform/parse-server/issues?utf8=%E2%9C%93&q=). Chances are that your issue has been reported or resolved before.
88

99
#### Environment Setup
1010

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,20 @@ PARSE_SERVER_MAX_UPLOAD_SIZE
135135

136136
```
137137

138+
##### Configuring S3 Adapter
139+
140+
You can use the following environment variable setup the S3 adapter
141+
142+
```js
143+
S3_ACCESS_KEY
144+
S3_SECRET_KEY
145+
S3_BUCKET
146+
S3_REGION
147+
S3_BUCKET_PREFIX
148+
S3_DIRECT_ACCESS
149+
150+
```
151+
138152
## Contributing
139153

140154
We really want Parse to be yours, to see it grow and thrive in the open source community. Please see the [Contributing to Parse Server guide](CONTRIBUTING.md).

spec/AdapterLoader.spec.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
var loadAdapter = require("../src/Adapters/AdapterLoader").loadAdapter;
33
var FilesAdapter = require("../src/Adapters/Files/FilesAdapter").default;
4+
var ParsePushAdapter = require("../src/Adapters/Push/ParsePushAdapter");
5+
var S3Adapter = require("../src/Adapters/Files/S3Adapter").default;
46

57
describe("AdapterLoader", ()=>{
68

@@ -84,4 +86,27 @@ describe("AdapterLoader", ()=>{
8486
}).not.toThrow("foo is required for that adapter");
8587
done();
8688
});
89+
90+
it("should load push adapter from options", (done) => {
91+
var options = {
92+
ios: {
93+
bundleId: 'bundle.id'
94+
}
95+
}
96+
expect(() => {
97+
var adapter = loadAdapter(undefined, ParsePushAdapter, options);
98+
expect(adapter.constructor).toBe(ParsePushAdapter);
99+
expect(adapter).not.toBe(undefined);
100+
}).not.toThrow();
101+
done();
102+
});
103+
104+
it("should load S3Adapter from direct passing", (done) => {
105+
var s3Adapter = new S3Adapter("key", "secret", "bucket")
106+
expect(() => {
107+
var adapter = loadAdapter(s3Adapter, FilesAdapter);
108+
expect(adapter).toBe(s3Adapter);
109+
}).not.toThrow();
110+
done();
111+
})
87112
});

spec/ParseAPI.spec.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,23 @@ describe('miscellaneous', function() {
967967
});
968968
});
969969

970+
it('beforeSave change propagates through the save response', (done) => {
971+
Parse.Cloud.beforeSave('ChangingObject', function(request, response) {
972+
request.object.set('foo', 'baz');
973+
response.success();
974+
});
975+
let obj = new Parse.Object('ChangingObject');
976+
obj.save({ foo: 'bar' }).then((objAgain) => {
977+
expect(objAgain.get('foo')).toEqual('baz');
978+
Parse.Cloud._removeHook("Triggers", "beforeSave", "ChangingObject");
979+
done();
980+
}, (e) => {
981+
Parse.Cloud._removeHook("Triggers", "beforeSave", "ChangingObject");
982+
fail('Should not have failed to save.');
983+
done();
984+
});
985+
});
986+
970987
it('dedupes an installation properly and returns updatedAt', (done) => {
971988
let headers = {
972989
'Content-Type': 'application/json',
@@ -995,4 +1012,32 @@ describe('miscellaneous', function() {
9951012
});
9961013
});
9971014

1015+
it('android login providing empty authData block works', (done) => {
1016+
let headers = {
1017+
'Content-Type': 'application/json',
1018+
'X-Parse-Application-Id': 'test',
1019+
'X-Parse-REST-API-Key': 'rest'
1020+
};
1021+
let data = {
1022+
username: 'pulse1989',
1023+
password: 'password1234',
1024+
authData: {}
1025+
};
1026+
let requestOptions = {
1027+
headers: headers,
1028+
url: 'http://localhost:8378/1/users',
1029+
body: JSON.stringify(data)
1030+
};
1031+
request.post(requestOptions, (error, response, body) => {
1032+
expect(error).toBe(null);
1033+
requestOptions.url = 'http://localhost:8378/1/login';
1034+
request.get(requestOptions, (error, response, body) => {
1035+
expect(error).toBe(null);
1036+
let b = JSON.parse(body);
1037+
expect(typeof b['sessionToken']).toEqual('string');
1038+
done();
1039+
});
1040+
});
1041+
});
1042+
9981043
});

spec/ParseRole.spec.js

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
1+
"use strict";
22

33
// Roles are not accessible without the master key, so they are not intended
44
// for use by clients. We can manually test them using the master key.
@@ -64,26 +64,30 @@ describe('Parse Role testing', () => {
6464

6565
var rolesNames = ["FooRole", "BarRole", "BazRole"];
6666

67-
var createRole = function(name, parent, user) {
67+
var createRole = function(name, sibling, user) {
6868
var role = new Parse.Role(name, new Parse.ACL());
6969
if (user) {
7070
var users = role.relation('users');
7171
users.add(user);
7272
}
73-
if (parent) {
74-
role.relation('roles').add(parent);
73+
if (sibling) {
74+
role.relation('roles').add(sibling);
7575
}
7676
return role.save({}, { useMasterKey: true });
7777
}
7878
var roleIds = {};
7979
createTestUser().then( (user) => {
80-
81-
return createRole(rolesNames[0], null, null).then( (aRole) => {
80+
// Put the user on the 1st role
81+
return createRole(rolesNames[0], null, user).then( (aRole) => {
8282
roleIds[aRole.get("name")] = aRole.id;
83+
// set the 1st role as a sibling of the second
84+
// user will should have 2 role now
8385
return createRole(rolesNames[1], aRole, null);
8486
}).then( (anotherRole) => {
8587
roleIds[anotherRole.get("name")] = anotherRole.id;
86-
return createRole(rolesNames[2], anotherRole, user);
88+
// set this role as a sibling of the last
89+
// the user should now have 3 roles
90+
return createRole(rolesNames[2], anotherRole, null);
8791
}).then( (lastRole) => {
8892
roleIds[lastRole.get("name")] = lastRole.id;
8993
var auth = new Auth({ config: new Config("test"), isMaster: true, user: user });
@@ -118,6 +122,80 @@ describe('Parse Role testing', () => {
118122
});
119123
});
120124
});
125+
126+
it("Should properly resolve roles", (done) => {
127+
let admin = new Parse.Role("Admin", new Parse.ACL());
128+
let moderator = new Parse.Role("Moderator", new Parse.ACL());
129+
let superModerator = new Parse.Role("SuperModerator", new Parse.ACL());
130+
let contentManager = new Parse.Role('ContentManager', new Parse.ACL());
131+
let superContentManager = new Parse.Role('SuperContentManager', new Parse.ACL());
132+
Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true}).then(() => {
133+
contentManager.getRoles().add([moderator, superContentManager]);
134+
moderator.getRoles().add([admin, superModerator]);
135+
superContentManager.getRoles().add(superModerator);
136+
return Parse.Object.saveAll([admin, moderator, contentManager, superModerator, superContentManager], {useMasterKey: true});
137+
}).then(() => {
138+
var auth = new Auth({ config: new Config("test"), isMaster: true });
139+
// For each role, fetch their sibling, what they inherit
140+
// return with result and roleId for later comparison
141+
let promises = [admin, moderator, contentManager, superModerator].map((role) => {
142+
return auth._getAllRoleNamesForId(role.id).then((result) => {
143+
return Parse.Promise.as({
144+
id: role.id,
145+
name: role.get('name'),
146+
roleIds: result
147+
});
148+
})
149+
});
150+
151+
return Parse.Promise.when(promises);
152+
}).then((results) => {
153+
results.forEach((result) => {
154+
let id = result.id;
155+
let roleIds = result.roleIds;
156+
if (id == admin.id) {
157+
expect(roleIds.length).toBe(2);
158+
expect(roleIds.indexOf(moderator.id)).not.toBe(-1);
159+
expect(roleIds.indexOf(contentManager.id)).not.toBe(-1);
160+
} else if (id == moderator.id) {
161+
expect(roleIds.length).toBe(1);
162+
expect(roleIds.indexOf(contentManager.id)).toBe(0);
163+
} else if (id == contentManager.id) {
164+
expect(roleIds.length).toBe(0);
165+
} else if (id == superModerator.id) {
166+
expect(roleIds.length).toBe(3);
167+
expect(roleIds.indexOf(moderator.id)).not.toBe(-1);
168+
expect(roleIds.indexOf(contentManager.id)).not.toBe(-1);
169+
expect(roleIds.indexOf(superContentManager.id)).not.toBe(-1);
170+
}
171+
});
172+
done();
173+
}).fail((err) => {
174+
console.error(err);
175+
done();
176+
})
177+
178+
});
179+
180+
it('can create role and query empty users', (done)=> {
181+
var roleACL = new Parse.ACL();
182+
roleACL.setPublicReadAccess(true);
183+
var role = new Parse.Role('subscribers', roleACL);
184+
role.save({}, {useMasterKey : true})
185+
.then((x)=>{
186+
var query = role.relation('users').query();
187+
query.find({useMasterKey : true})
188+
.then((users)=>{
189+
done();
190+
}, (e)=>{
191+
fail('should not have errors');
192+
done();
193+
});
194+
}, (e) => {
195+
console.log(e);
196+
fail('should not have errored');
197+
});
198+
});
121199

122200
});
123201

spec/RestCreate.spec.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,17 +175,26 @@ describe('rest create', () => {
175175
}
176176
}
177177
};
178+
var newUserSignedUpByFacebookObjectId;
178179
rest.create(config, auth.nobody(config), '_User', data)
179180
.then((r) => {
180181
expect(typeof r.response.objectId).toEqual('string');
181182
expect(typeof r.response.createdAt).toEqual('string');
182183
expect(typeof r.response.sessionToken).toEqual('string');
184+
newUserSignedUpByFacebookObjectId = r.response.objectId;
183185
return rest.create(config, auth.nobody(config), '_User', data);
184186
}).then((r) => {
185187
expect(typeof r.response.objectId).toEqual('string');
186188
expect(typeof r.response.createdAt).toEqual('string');
187189
expect(typeof r.response.username).toEqual('string');
188190
expect(typeof r.response.updatedAt).toEqual('string');
191+
expect(r.response.objectId).toEqual(newUserSignedUpByFacebookObjectId);
192+
return rest.find(config, auth.master(config),
193+
'_Session', {sessionToken: r.response.sessionToken});
194+
}).then((response) => {
195+
expect(response.results.length).toEqual(1);
196+
var output = response.results[0];
197+
expect(output.user.objectId).toEqual(newUserSignedUpByFacebookObjectId);
189198
done();
190199
});
191200
});

spec/Schema.spec.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,38 @@ describe('Schema', () => {
577577
});
578578
});
579579

580+
it('can delete relation field when related _Join collection not exist', done => {
581+
config.database.loadSchema()
582+
.then(schema => {
583+
schema.addClassIfNotExists('NewClass', {
584+
relationField: {type: 'Relation', targetClass: '_User'}
585+
})
586+
.then(mongoObj => {
587+
expect(mongoObj).toEqual({
588+
_id: 'NewClass',
589+
objectId: 'string',
590+
updatedAt: 'string',
591+
createdAt: 'string',
592+
relationField: 'relation<_User>',
593+
});
594+
})
595+
.then(() => config.database.collectionExists('_Join:relationField:NewClass'))
596+
.then(exist => {
597+
expect(exist).toEqual(false);
598+
})
599+
.then(() => schema.deleteField('relationField', 'NewClass', config.database))
600+
.then(() => schema.reloadData())
601+
.then(() => {
602+
expect(schema['data']['NewClass']).toEqual({
603+
objectId: 'string',
604+
updatedAt: 'string',
605+
createdAt: 'string'
606+
});
607+
done();
608+
});
609+
});
610+
});
611+
580612
it('can delete string fields and resave as number field', done => {
581613
Parse.Object.disableSingleInstance();
582614
var obj1 = hasAllPODobject();

src/Adapters/AdapterLoader.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,8 @@ export function loadAdapter(adapter, defaultAdapter, options) {
2828
return loadAdapter(adapter.class, undefined, adapter.options);
2929
} else if (adapter.adapter) {
3030
return loadAdapter(adapter.adapter, undefined, adapter.options);
31-
} else {
32-
// Try to load the defaultAdapter with the options
33-
// The default adapter should throw if the options are
34-
// incompatible
35-
try {
36-
return loadAdapter(defaultAdapter, undefined, adapter);
37-
} catch (e) {};
3831
}
39-
// return the adapter as is as it's unusable otherwise
32+
// return the adapter as provided
4033
return adapter;
4134
}
4235

src/Adapters/Files/S3Adapter.js

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,33 @@ import requiredParameter from '../../requiredParameter';
88

99
const DEFAULT_S3_REGION = "us-east-1";
1010

11-
function parseS3AdapterOptions(...options) {
12-
if (options.length === 1 && typeof options[0] == "object") {
13-
return options;
11+
function requiredOrFromEnvironment(env, name) {
12+
let environmentVariable = process.env[env];
13+
if (!environmentVariable) {
14+
requiredParameter(`S3Adapter requires an ${name}`);
1415
}
15-
16-
const additionalOptions = options[3] || {};
17-
18-
return {
19-
accessKey: options[0],
20-
secretKey: options[1],
21-
bucket: options[2],
22-
region: additionalOptions.region
16+
return environmentVariable;
17+
}
18+
19+
function fromEnvironmentOrDefault(env, defaultValue) {
20+
let environmentVariable = process.env[env];
21+
if (environmentVariable) {
22+
return environmentVariable;
2323
}
24+
return defaultValue;
2425
}
2526

2627
export class S3Adapter extends FilesAdapter {
2728
// Creates an S3 session.
2829
// Providing AWS access and secret keys is mandatory
2930
// Region and bucket will use sane defaults if omitted
3031
constructor(
31-
accessKey = requiredParameter('S3Adapter requires an accessKey'),
32-
secretKey = requiredParameter('S3Adapter requires a secretKey'),
33-
bucket,
34-
{ region = DEFAULT_S3_REGION,
35-
bucketPrefix = '',
36-
directAccess = false } = {}) {
32+
accessKey = requiredOrFromEnvironment('S3_ACCESS_KEY', 'accessKey'),
33+
secretKey = requiredOrFromEnvironment('S3_SECRET_KEY', 'secretKey'),
34+
bucket = fromEnvironmentOrDefault('S3_BUCKET', undefined),
35+
{ region = fromEnvironmentOrDefault('S3_REGION', DEFAULT_S3_REGION),
36+
bucketPrefix = fromEnvironmentOrDefault('S3_BUCKET_PREFIX', ''),
37+
directAccess = fromEnvironmentOrDefault('S3_DIRECT_ACCESS', false) } = {}) {
3738
super();
3839

3940
this._region = region;

0 commit comments

Comments
 (0)