diff --git a/CHANGELOG.md b/CHANGELOG.md index e7e83371..c27fb140 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## Parse Server Changelog +### 2.1.2 (2/19/2016) + +* Change: The S3 file adapter constructor requires a bucket name +* Fix: Parse Query should throw if improperly encoded +* Fix: Issue where roles were not used in some requests +* Fix: serverURL will no longer default to api.parse.com/1 + ### 2.1.1 (2/18/2016) * Experimental: Schemas API support for DELETE operations diff --git a/spec/ParseRole.spec.js b/spec/ParseRole.spec.js index 44476f1b..919e0b55 100644 --- a/spec/ParseRole.spec.js +++ b/spec/ParseRole.spec.js @@ -49,7 +49,7 @@ describe('Parse Role testing', () => { }).then((x) => { x.set('foo', 'baz'); // This should fail: - return x.save(); + return x.save({},{sessionToken: ""}); }).then((x) => { fail('Should not have been able to save.'); }, (e) => { diff --git a/spec/RestQuery.spec.js b/spec/RestQuery.spec.js index b93a07d5..279f45f6 100644 --- a/spec/RestQuery.spec.js +++ b/spec/RestQuery.spec.js @@ -4,6 +4,9 @@ var cache = require('../src/cache'); var Config = require('../src/Config'); var rest = require('../src/rest'); +var querystring = require('querystring'); +var request = require('request'); + var config = new Config('test'); var nobody = auth.nobody(config); @@ -92,4 +95,49 @@ describe('rest query', () => { }).catch((error) => { console.log(error); }); }); + it('query with wrongly encoded parameter', (done) => { + rest.create(config, nobody, 'TestParameterEncode', {foo: 'bar'} + ).then(() => { + return rest.create(config, nobody, + 'TestParameterEncode', {foo: 'baz'}); + }).then(() => { + var headers = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest' + }; + request.get({ + headers: headers, + url: 'http://localhost:8378/1/classes/TestParameterEncode?' + + querystring.stringify({ + where: '{"foo":{"$ne": "baz"}}', + limit: 1 + }).replace('=', '%3D'), + }, (error, response, body) => { + expect(error).toBe(null); + var b = JSON.parse(body); + expect(b.code).toEqual(Parse.Error.INVALID_QUERY); + expect(b.error).toEqual('Improper encode of parameter'); + done(); + }); + }).then(() => { + var headers = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest' + }; + request.get({ + headers: headers, + url: 'http://localhost:8378/1/classes/TestParameterEncode?' + + querystring.stringify({ + limit: 1 + }).replace('=', '%3D'), + }, (error, response, body) => { + expect(error).toBe(null); + var b = JSON.parse(body); + expect(b.code).toEqual(Parse.Error.INVALID_QUERY); + expect(b.error).toEqual('Improper encode of parameter'); + done(); + }); + }); + }); + }); diff --git a/src/Auth.js b/src/Auth.js index ad905654..27bbf885 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -80,7 +80,7 @@ Auth.prototype.getUserRoles = function() { return Promise.resolve(this.userRoles); } if (this.rolePromise) { - return rolePromise; + return this.rolePromise; } this.rolePromise = this._loadRoles(); return this.rolePromise; diff --git a/src/RestWrite.js b/src/RestWrite.js index 54f5cfc9..777973d7 100644 --- a/src/RestWrite.js +++ b/src/RestWrite.js @@ -27,6 +27,7 @@ function RestWrite(config, auth, className, query, data, originalData) { this.auth = auth; this.className = className; this.storage = {}; + this.runOptions = {}; if (!query && data.objectId) { throw new Parse.Error(Parse.Error.INVALID_KEY_NAME, 'objectId ' + @@ -66,6 +67,8 @@ function RestWrite(config, auth, className, query, data, originalData) { // status and location are optional. RestWrite.prototype.execute = function() { return Promise.resolve().then(() => { + return this.getUserAndRoleACL(); + }).then(() => { return this.validateSchema(); }).then(() => { return this.handleInstallation(); @@ -88,6 +91,25 @@ RestWrite.prototype.execute = function() { }); }; +// Uses the Auth object to get the list of roles, adds the user id +RestWrite.prototype.getUserAndRoleACL = function() { + if (this.auth.isMaster) { + return Promise.resolve(); + } + + this.runOptions.acl = ['*']; + + if( this.auth.user ){ + return this.auth.getUserRoles().then((roles) => { + roles.push(this.auth.user.id); + this.runOptions.acl = this.runOptions.acl.concat(roles); + return Promise.resolve(); + }); + }else{ + return Promise.resolve(); + } +}; + // Validates this operation against the schema. RestWrite.prototype.validateSchema = function() { return this.config.database.validateObject(this.className, this.data); @@ -690,18 +712,10 @@ RestWrite.prototype.runDatabaseOperation = function() { throw new Parse.Error(Parse.Error.INVALID_ACL, 'Invalid ACL.'); } - var options = {}; - if (!this.auth.isMaster) { - options.acl = ['*']; - if (this.auth.user) { - options.acl.push(this.auth.user.id); - } - } - if (this.query) { // Run an update return this.config.database.update( - this.className, this.query, this.data, options).then((resp) => { + this.className, this.query, this.data, this.runOptions).then((resp) => { this.response = resp; this.response.updatedAt = this.updatedAt; }); @@ -714,7 +728,7 @@ RestWrite.prototype.runDatabaseOperation = function() { this.data.ACL = ACL; } // Run a create - return this.config.database.create(this.className, this.data, options) + return this.config.database.create(this.className, this.data, this.runOptions) .then(() => { var resp = { objectId: this.data.objectId, diff --git a/src/Routers/ClassesRouter.js b/src/Routers/ClassesRouter.js index 95a27ef1..9b5b20d7 100644 --- a/src/Routers/ClassesRouter.js +++ b/src/Routers/ClassesRouter.js @@ -2,11 +2,22 @@ import PromiseRouter from '../PromiseRouter'; import rest from '../rest'; +import url from 'url'; + export class ClassesRouter { // Returns a promise that resolves to a {response} object. handleFind(req) { let body = Object.assign(req.body, req.query); let options = {}; + let allowConstraints = ['skip', 'limit', 'order', 'count', 'keys', + 'include', 'redirectClassNameForKey', 'where']; + + for (var key in body) { + if (allowConstraints.indexOf(key) === -1) { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Improper encode of parameter'); + } + } + if (body.skip) { options.skip = Number(body.skip); } diff --git a/src/index.js b/src/index.js index 36eda1b9..ecd4f967 100644 --- a/src/index.js +++ b/src/index.js @@ -72,7 +72,7 @@ function ParseServer({ facebookAppIds = [], enableAnonymousUsers = true, oauth = {}, - serverURL, + serverURL = '', }) { if (!appId || !masterKey) { throw 'You must provide an appId and masterKey!'; @@ -128,9 +128,7 @@ function ParseServer({ // Initialize the node client SDK automatically Parse.initialize(appId, javascriptKey, masterKey); - if (serverURL) { - Parse.serverURL = serverURL; - } + Parse.serverURL = serverURL; // This app serves the Parse API directly. // It's the equivalent of https://api.parse.com/1 in the hosted Parse API. diff --git a/src/rest.js b/src/rest.js index 552fa6be..094e8ab6 100644 --- a/src/rest.js +++ b/src/rest.js @@ -56,12 +56,19 @@ function del(config, auth, className, objectId) { }); } return Promise.resolve({}); + }).then(() => { + if (!auth.isMaster) { + return auth.getUserRoles(); + }else{ + return Promise.resolve(); + } }).then(() => { var options = {}; if (!auth.isMaster) { options.acl = ['*']; if (auth.user) { options.acl.push(auth.user.id); + options.acl = options.acl.concat(auth.userRoles); } }