diff --git a/services/real-time/app/js/WebsocketController.js b/services/real-time/app/js/WebsocketController.js index 90128be1e5..2cbd54b4c8 100644 --- a/services/real-time/app/js/WebsocketController.js +++ b/services/real-time/app/js/WebsocketController.js @@ -576,6 +576,14 @@ export default WebsocketController = { ) return callback(error) } + if (update.doc && update.doc !== docId) { + return callback( + new Errors.CodedError( + 'update.doc must be identical to docId parameter in applyOtUpdate(docId, update)' + ) + ) + } + update.doc = docId if (!update.meta) { update.meta = {} } diff --git a/services/real-time/test/acceptance/js/ApplyUpdateTests.js b/services/real-time/test/acceptance/js/ApplyUpdateTests.js index cf854d11fe..dd9aa544dd 100644 --- a/services/real-time/test/acceptance/js/ApplyUpdateTests.js +++ b/services/real-time/test/acceptance/js/ApplyUpdateTests.js @@ -440,6 +440,97 @@ describe('applyOtUpdate', function () { }) }) + describe('when authorized with an edit update to a missing doc field', function () { + before(function (done) { + return async.series( + [ + cb => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'readAndWrite', + }, + (e, { project_id: projectId, user_id: userId }) => { + this.project_id = projectId + this.user_id = userId + return cb(e) + } + ) + }, + + cb => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id: docId }) => { + this.doc_id = docId + return cb(e) + } + ) + }, + + cb => { + this.client = RealTimeClient.connect(this.project_id, cb) + }, + + cb => { + return this.client.emit('joinDoc', this.doc_id, cb) + }, + + cb => { + return this.client.emit( + 'applyOtUpdate', + this.doc_id, + this.update, + error => { + this.error = error + return cb() + } + ) + }, + ], + done + ) + }) + + after(function (done) { + async.series( + [ + cb => clearPendingUpdatesList(cb), + cb => + rclient.del( + redisSettings.documentupdater.key_schema.pendingUpdates({ + doc_id: this.doc_id, + }), + cb + ), + ], + done + ) + }) + + it('should not return an error', function () { + expect(this.error).to.not.exist + }) + + it('should put the update in redis', function (done) { + rclient.lrange( + redisSettings.documentupdater.key_schema.pendingUpdates({ + doc_id: this.doc_id, + }), + 0, + -1, + (error, entries) => { + if (error) return done(error) + entries.length.should.equal(1) + const update = JSON.parse(entries[0]) + update.should.include({ doc: this.doc_id }) + return done() + } + ) + return null + }) + }) + describe('when authorized with an edit update to an invalid doc', function () { before(function (done) { return async.series( @@ -518,6 +609,80 @@ describe('applyOtUpdate', function () { }) }) + describe('when authorized with an edit update to a different doc', function () { + before(function (done) { + return async.series( + [ + cb => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'readAndWrite', + }, + (e, { project_id: projectId, user_id: userId }) => { + this.project_id = projectId + this.user_id = userId + return cb(e) + } + ) + }, + + cb => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id: docId }) => { + this.doc_id = docId + return cb(e) + } + ) + }, + + cb => { + this.client = RealTimeClient.connect(this.project_id, cb) + }, + + cb => { + return this.client.emit('joinDoc', this.doc_id, cb) + }, + + cb => { + return this.client.emit( + 'applyOtUpdate', + this.doc_id, + { doc: 'other-doc' }, + error => { + this.error = error + return cb() + } + ) + }, + ], + done + ) + }) + + it('should return an error', function () { + expect(this.error).to.deep.equal({ + message: + 'update.doc must be identical to docId parameter in applyOtUpdate(docId, update)', + }) + }) + + it('should not put the update in redis', function (done) { + rclient.llen( + redisSettings.documentupdater.key_schema.pendingUpdates({ + doc_id: this.doc_id, + }), + (error, len) => { + if (error) return done(error) + len.should.equal(0) + return done() + } + ) + return null + }) + }) + describe('when authorized with an invalid edit update', function () { before(function (done) { return async.series(