From 096c172caa3eea28d2766c7283419f49dadea47e Mon Sep 17 00:00:00 2001 From: wangmengyan95 Date: Tue, 2 Feb 2016 19:51:40 -0800 Subject: [PATCH] Add push parameter checking and query installation --- index.js | 2 +- package.json | 3 +- push.js | 120 +++++++++++++++++++++++++-- spec/push.spec.js | 206 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 322 insertions(+), 9 deletions(-) create mode 100644 spec/push.spec.js diff --git a/index.js b/index.js index 9f8a5a4702..a07c8476a9 100644 --- a/index.js +++ b/index.js @@ -111,7 +111,7 @@ function ParseServer(args) { router.merge(require('./sessions')); router.merge(require('./roles')); router.merge(require('./analytics')); - router.merge(require('./push')); + router.merge(require('./push').router); router.merge(require('./installations')); router.merge(require('./functions')); router.merge(require('./schemas')); diff --git a/package.json b/package.json index b6039e572e..7b7facf31c 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "mongodb": "~2.1.0", "multer": "^1.1.0", "parse": "^1.7.0", + "moment": "^2.11.1", "request": "^2.65.0" }, "devDependencies": { @@ -30,7 +31,7 @@ }, "scripts": { "pretest": "MONGODB_VERSION=${MONGODB_VERSION:=3.0.8} mongodb-runner start", - "test": "TESTING=1 ./node_modules/.bin/istanbul cover --include-all-sources -x **/spec/** ./node_modules/.bin/jasmine", + "test": "NODE_ENV=test TESTING=1 ./node_modules/.bin/istanbul cover --include-all-sources -x **/spec/** ./node_modules/.bin/jasmine", "posttest": "mongodb-runner stop", "start": "./bin/parse-server" }, diff --git a/push.js b/push.js index 08a192c474..7b05757363 100644 --- a/push.js +++ b/push.js @@ -2,17 +2,123 @@ var Parse = require('parse/node').Parse, PromiseRouter = require('./PromiseRouter'), - rest = require('./rest'); + rest = require('./rest'), + moment = require('moment'); -var router = new PromiseRouter(); +var validPushTypes = ['ios', 'android']; + +function handlePushWithoutQueue(req) { + validateMasterKey(req); + var where = getQueryCondition(req); + validateDeviceType(where); + req.expirationTime = getExpirationTime(req); + return rest.find(req.config, req.auth, '_Installation', where).then(function(response) { + throw new Parse.Error(Parse.Error.COMMAND_UNAVAILABLE, + 'This path is not implemented yet.'); + }); +} + +/** + * Check whether the deviceType parameter in qury condition is valid or not. + * @param {Object} where A query condition + */ +function validateDeviceType(where) { + var where = where || {}; + var deviceTypeField = where.deviceType || {}; + var deviceTypes = []; + if (typeof deviceTypeField === 'string') { + deviceTypes.push(deviceTypeField); + } else if (typeof deviceTypeField['$in'] === 'array') { + deviceTypes.concat(deviceTypeField['$in']); + } + for (var i = 0; i < deviceTypes.length; i++) { + var deviceType = deviceTypes[i]; + if (validPushTypes.indexOf(deviceType) < 0) { + throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, + deviceType + ' is not supported push type.'); + } + } +} + +/** + * Get expiration time from the request body. + * @param {Object} request A request object + * @returns {Number|undefined} The expiration time if it exists in the request + */ +function getExpirationTime(req) { + var body = req.body || {}; + var hasExpirationTime = !!body['expiration_time']; + if (!hasExpirationTime) { + return; + } + var expirationTimeParam = body['expiration_time']; + var expirationTime; + if (typeof expirationTimeParam === 'number') { + expirationTime = new Date(expirationTimeParam * 1000); + } else if (typeof expirationTimeParam === 'string') { + expirationTime = new Date(expirationTimeParam); + } else { + throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, + body['expiration_time'] + ' is not valid time.'); + } + // Check expirationTime is valid or not, if it is not valid, expirationTime is NaN + if (!isFinite(expirationTime)) { + throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, + body['expiration_time'] + ' is not valid time.'); + } + return expirationTime.valueOf(); +} +/** + * Get query condition from the request body. + * @param {Object} request A request object + * @returns {Object} The query condition, the where field in a query api call + */ +function getQueryCondition(req) { + var body = req.body || {}; + var hasWhere = typeof body.where !== 'undefined'; + var hasChannels = typeof body.channels !== 'undefined'; + var where; + if (hasWhere && hasChannels) { + throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, + 'Channels and query can not be set at the same time.'); + } else if (hasWhere) { + where = body.where; + } else if (hasChannels) { + where = { + "channels": { + "$in": body.channels + } + } + } else { + throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, + 'Channels and query should be set at least one.'); + } + return where; +} -function notImplementedYet(req) { - throw new Parse.Error(Parse.Error.COMMAND_UNAVAILABLE, - 'This path is not implemented yet.'); +/** + * Check whether the api call has master key or not. + * @param {Object} request A request object + */ +function validateMasterKey(req) { + if (req.info.masterKey !== req.config.masterKey) { + throw new Parse.Error(Parse.Error.PUSH_MISCONFIGURED, + 'Master key is invalid, you should only use master key to send push'); + } } -router.route('POST','/push', notImplementedYet); +var router = new PromiseRouter(); +router.route('POST','/push', handlePushWithoutQueue); + +module.exports = { + router: router +} -module.exports = router; \ No newline at end of file +if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') { + module.exports.getQueryCondition = getQueryCondition; + module.exports.validateMasterKey = validateMasterKey; + module.exports.getExpirationTime = getExpirationTime; + module.exports.validateDeviceType = validateDeviceType; +} diff --git a/spec/push.spec.js b/spec/push.spec.js new file mode 100644 index 0000000000..ba5b533bbe --- /dev/null +++ b/spec/push.spec.js @@ -0,0 +1,206 @@ +var push = require('../push'); + +describe('push', () => { + it('can check valid master key of request', (done) => { + // Make mock request + var request = { + info: { + masterKey: 'masterKey' + }, + config: { + masterKey: 'masterKey' + } + } + + expect(() => { + push.validateMasterKey(request); + }).not.toThrow(); + done(); + }); + + it('can check invalid master key of request', (done) => { + // Make mock request + var request = { + info: { + masterKey: 'masterKey' + }, + config: { + masterKey: 'masterKeyAgain' + } + } + + expect(() => { + push.validateMasterKey(request); + }).toThrow(); + done(); + }); + + it('can get query condition when channels is set', (done) => { + // Make mock request + var request = { + body: { + channels: ['Giants', 'Mets'] + } + } + + var where = push.getQueryCondition(request); + expect(where).toEqual({ + 'channels': { + '$in': ['Giants', 'Mets'] + } + }); + done(); + }); + + it('can get query condition when where is set', (done) => { + // Make mock request + var request = { + body: { + 'where': { + 'injuryReports': true + } + } + } + + var where = push.getQueryCondition(request); + expect(where).toEqual({ + 'injuryReports': true + }); + done(); + }); + + it('can get query condition when nothing is set', (done) => { + // Make mock request + var request = { + body: { + } + } + + expect(function() { + push.getQueryCondition(request); + }).toThrow(); + done(); + }); + + it('can throw on getQueryCondition when channels and where are set', (done) => { + // Make mock request + var request = { + body: { + 'channels': { + '$in': ['Giants', 'Mets'] + }, + 'where': { + 'injuryReports': true + } + } + } + + expect(function() { + push.getQueryCondition(request); + }).toThrow(); + done(); + }); + + it('can validate device type when no device type is set', (done) => { + // Make query condition + var where = { + } + + expect(function(){ + push.validateDeviceType(where); + }).not.toThrow(); + done(); + }); + + it('can validate device type when single valid device type is set', (done) => { + // Make query condition + var where = { + 'deviceType': 'ios' + } + + expect(function(){ + push.validateDeviceType(where); + }).not.toThrow(); + done(); + }); + + it('can validate device type when multiple valid device types are set', (done) => { + // Make query condition + var where = { + 'deviceType': { + '$in': ['android', 'ios'] + } + } + + expect(function(){ + push.validateDeviceType(where); + }).not.toThrow(); + done(); + }); + + it('can throw on validateDeviceType when single invalid device type is set', (done) => { + // Make query condition + var where = { + 'deviceType': 'osx' + } + + expect(function(){ + push.validateDeviceType(where); + }).toThrow(); + done(); + }); + + it('can throw on validateDeviceType when single invalid device type is set', (done) => { + // Make query condition + var where = { + 'deviceType': 'osx' + } + + expect(function(){ + push.validateDeviceType(where) + }).toThrow(); + done(); + }); + + it('can get expiration time in string format', (done) => { + // Make mock request + var timeStr = '2015-03-19T22:05:08Z'; + var request = { + body: { + 'expiration_time': timeStr + } + } + + var time = push.getExpirationTime(request); + expect(time).toEqual(new Date(timeStr).valueOf()); + done(); + }); + + it('can get expiration time in number format', (done) => { + // Make mock request + var timeNumber = 1426802708; + var request = { + body: { + 'expiration_time': timeNumber + } + } + + var time = push.getExpirationTime(request); + expect(time).toEqual(timeNumber * 1000); + done(); + }); + + it('can throw on getExpirationTime in invalid format', (done) => { + // Make mock request + var request = { + body: { + 'expiration_time': 'abcd' + } + } + + expect(function(){ + push.getExpirationTime(request); + }).toThrow(); + done(); + }); +});