Skip to content

Commit fe4c1cc

Browse files
committed
Add push parameter checking and query installation
1 parent 72fa1f2 commit fe4c1cc

File tree

4 files changed

+310
-9
lines changed

4 files changed

+310
-9
lines changed

index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ function ParseServer(args) {
111111
router.merge(require('./sessions'));
112112
router.merge(require('./roles'));
113113
router.merge(require('./analytics'));
114-
router.merge(require('./push'));
114+
router.merge(require('./push').router);
115115
router.merge(require('./installations'));
116116
router.merge(require('./functions'));
117117
router.merge(require('./schemas'));

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"mongodb": "~2.1.0",
2020
"multer": "^1.1.0",
2121
"parse": "^1.7.0",
22+
"moment": "^2.11.1",
2223
"request": "^2.65.0"
2324
},
2425
"devDependencies": {
@@ -30,7 +31,7 @@
3031
},
3132
"scripts": {
3233
"pretest": "MONGODB_VERSION=${MONGODB_VERSION:=3.0.8} mongodb-runner start",
33-
"test": "TESTING=1 ./node_modules/.bin/istanbul cover --include-all-sources -x **/spec/** ./node_modules/.bin/jasmine",
34+
"test": "NODE_ENV=test TESTING=1 ./node_modules/.bin/istanbul cover --include-all-sources -x **/spec/** ./node_modules/.bin/jasmine",
3435
"posttest": "mongodb-runner stop",
3536
"start": "./bin/parse-server"
3637
},

push.js

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,112 @@
22

33
var Parse = require('parse/node').Parse,
44
PromiseRouter = require('./PromiseRouter'),
5-
rest = require('./rest');
5+
rest = require('./rest'),
6+
moment = require('moment');
67

7-
var router = new PromiseRouter();
8+
var validPushTypes = ['ios', 'android'];
9+
10+
function handlePush(req) {
11+
validateMasterKey(req);
12+
var where = getQueryCondition(req);
13+
validateDeviceType(where);
14+
req.expirationTime = getExpirationTime(req);
15+
return rest.find(req.config, req.auth, '_Installation', where).then(function(response) {
16+
throw new Parse.Error(Parse.Error.COMMAND_UNAVAILABLE,
17+
'This path is not implemented yet.');
18+
});
19+
}
20+
21+
/**
22+
* Check whether the deviceType parameter in qury condition is valid or not.
23+
* @param {Object} where A query condition
24+
*/
25+
function validateDeviceType(where) {
26+
var where = where || {};
27+
var deviceTypeField = where.deviceType || {};
28+
var deviceTypes = [];
29+
if (typeof deviceTypeField === 'string') {
30+
deviceTypes.push(deviceTypeField);
31+
} else if (typeof deviceTypeField['$in'] === 'array') {
32+
deviceTypes.concat(deviceTypeField['$in']);
33+
}
34+
for (var i = 0; i < deviceTypes.length; i++) {
35+
var deviceType = deviceTypes[i];
36+
if (validPushTypes.indexOf(deviceType) < 0) {
37+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
38+
deviceType + ' is not supported push type.');
39+
}
40+
}
41+
}
42+
43+
/**
44+
* Get expiration time from the request body.
45+
* @param {Object} request A request object
46+
* @returns {Number|undefined} The expiration time if it exists in the request
47+
*/
48+
function getExpirationTime(req) {
49+
var body = req.body || {};
50+
var hasExpirationTime = !!body['expiration_time'];
51+
if (!hasExpirationTime) {
52+
return;
53+
}
54+
expirationTime = moment(new Date(body['expiration_time']));
55+
if (!expirationTime.isValid()) {
56+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
57+
body['expiration_time'] + ' is not valid time.');
58+
}
59+
return expirationTime.valueOf();
60+
}
861

62+
/**
63+
* Get query condition from the request body.
64+
* @param {Object} request A request object
65+
* @returns {Object} The query condition, the where field in a query api call
66+
*/
67+
function getQueryCondition(req) {
68+
var body = req.body || {};
69+
var hasWhere = typeof body.where !== 'undefined';
70+
var hasChannels = typeof body.channels !== 'undefined';
971

72+
var where;
73+
if (hasWhere && hasChannels) {
74+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
75+
'Channels and query can not be set at the same time.');
76+
} else if (hasWhere) {
77+
where = body.where;
78+
} else if (hasChannels) {
79+
where = {
80+
"channels": {
81+
"$in": body.channels
82+
}
83+
}
84+
} else {
85+
where = {};
86+
}
87+
return where;
88+
}
1089

11-
function notImplementedYet(req) {
12-
throw new Parse.Error(Parse.Error.COMMAND_UNAVAILABLE,
13-
'This path is not implemented yet.');
90+
/**
91+
* Check whether the api call has master key or not.
92+
* @param {Object} request A request object
93+
*/
94+
function validateMasterKey(req) {
95+
if (req.info.masterKey !== req.config.masterKey) {
96+
throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED,
97+
'Master key is invalid, you should only use master key to send push');
98+
}
1499
}
15100

16-
router.route('POST','/push', notImplementedYet);
101+
var router = new PromiseRouter();
102+
router.route('POST','/push', handlePush);
103+
104+
module.exports = {
105+
router: router
106+
}
17107

18-
module.exports = router;
108+
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
109+
module.exports.getQueryCondition = getQueryCondition;
110+
module.exports.validateMasterKey = validateMasterKey;
111+
module.exports.getExpirationTime = getExpirationTime;
112+
module.exports.validateDeviceType = validateDeviceType;
113+
}

spec/push.spec.js

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
var push = require('../push');
2+
3+
describe('push', () => {
4+
it('can check valid master key of request', (done) => {
5+
// Make mock request
6+
var request = {
7+
info: {
8+
masterKey: 'masterKey'
9+
},
10+
config: {
11+
masterKey: 'masterKey'
12+
}
13+
}
14+
15+
expect(() => {
16+
push.validateMasterKey(request);
17+
}).not.toThrow();
18+
done();
19+
});
20+
21+
it('can check invalid master key of request', (done) => {
22+
// Make mock request
23+
var request = {
24+
info: {
25+
masterKey: 'masterKey'
26+
},
27+
config: {
28+
masterKey: 'masterKeyAgain'
29+
}
30+
}
31+
32+
expect(() => {
33+
push.validateMasterKey(request);
34+
}).toThrow();
35+
done();
36+
});
37+
38+
it('can get query condition when channels is set', (done) => {
39+
// Make mock request
40+
var request = {
41+
body: {
42+
channels: ['Giants', 'Mets']
43+
}
44+
}
45+
46+
var where = push.getQueryCondition(request);
47+
expect(where).toEqual({
48+
'channels': {
49+
'$in': ['Giants', 'Mets']
50+
}
51+
});
52+
done();
53+
});
54+
55+
it('can get query condition when where is set', (done) => {
56+
// Make mock request
57+
var request = {
58+
body: {
59+
'where': {
60+
'injuryReports': true
61+
}
62+
}
63+
}
64+
65+
var where = push.getQueryCondition(request);
66+
expect(where).toEqual({
67+
'injuryReports': true
68+
});
69+
done();
70+
});
71+
72+
it('can get query condition when nothing is set', (done) => {
73+
// Make mock request
74+
var request = {
75+
body: {
76+
}
77+
}
78+
79+
var where = push.getQueryCondition(request);
80+
expect(where).toEqual({});
81+
done();
82+
});
83+
84+
it('can throw on getQueryCondition when channels and where are set', (done) => {
85+
// Make mock request
86+
var request = {
87+
body: {
88+
'channels': {
89+
'$in': ['Giants', 'Mets']
90+
},
91+
'where': {
92+
'injuryReports': true
93+
}
94+
}
95+
}
96+
97+
expect(function() {
98+
push.getQueryCondition(request);
99+
}).toThrow();
100+
done();
101+
});
102+
103+
it('can validate device type when no device type is set', (done) => {
104+
// Make query condition
105+
var where = {
106+
}
107+
108+
expect(function(){
109+
push.validateDeviceType(where);
110+
}).not.toThrow();
111+
done();
112+
});
113+
114+
it('can validate device type when single valid device type is set', (done) => {
115+
// Make query condition
116+
var where = {
117+
'deviceType': 'ios'
118+
}
119+
120+
expect(function(){
121+
push.validateDeviceType(where);
122+
}).not.toThrow();
123+
done();
124+
});
125+
126+
it('can validate device type when multiple valid device types are set', (done) => {
127+
// Make query condition
128+
var where = {
129+
'deviceType': {
130+
'$in': ['android', 'ios']
131+
}
132+
}
133+
134+
expect(function(){
135+
push.validateDeviceType(where);
136+
}).not.toThrow();
137+
done();
138+
});
139+
140+
it('can throw on validateDeviceType when single invalid device type is set', (done) => {
141+
// Make query condition
142+
var where = {
143+
'deviceType': 'osx'
144+
}
145+
146+
expect(function(){
147+
push.validateDeviceType(where);
148+
}).toThrow();
149+
done();
150+
});
151+
152+
it('can throw on validateDeviceType when single invalid device type is set', (done) => {
153+
// Make query condition
154+
var where = {
155+
'deviceType': 'osx'
156+
}
157+
158+
expect(function(){
159+
push.validateDeviceType(where)
160+
}).toThrow();
161+
done();
162+
});
163+
164+
it('can get expiration time in string format', (done) => {
165+
// Make mock request
166+
var timeStr = '2015-03-19T22:05:08Z';
167+
var request = {
168+
body: {
169+
'expiration_time': timeStr
170+
}
171+
}
172+
173+
var time = push.getExpirationTime(request);
174+
expect(time).toEqual(new Date(timeStr).valueOf());
175+
done();
176+
});
177+
178+
it('can get expiration time in number format', (done) => {
179+
// Make mock request
180+
var timeNumber = 1426802708000;
181+
var request = {
182+
body: {
183+
'expiration_time': timeNumber
184+
}
185+
}
186+
187+
var time = push.getExpirationTime(request);
188+
expect(time).toEqual(timeNumber);
189+
done();
190+
});
191+
192+
it('can throw on getExpirationTime in invalid format', (done) => {
193+
// Make mock request
194+
var request = {
195+
body: {
196+
'expiration_time': 'abcd'
197+
}
198+
}
199+
200+
expect(function(){
201+
push.getExpirationTime(request);
202+
}).toThrow();
203+
done();
204+
});
205+
});

0 commit comments

Comments
 (0)