From bff580701322e2100e484989c476d583d26af38a Mon Sep 17 00:00:00 2001 From: Djavid Gabibiyan Date: Fri, 6 Mar 2020 21:14:38 +0500 Subject: [PATCH] fix: do not allow to change ownerId and entropy * fix: validate components of the `Document.$id` when replacing * chore: split errors --- .../data/validateDocumentsSTDataFactory.js | 14 +++++ lib/errors/DocumentEntropyMismatchError.js | 34 ++++++++++++ lib/errors/DocumentOwnerIdMismatchError.js | 34 ++++++++++++ .../validateDocumentsSTDataFactory.spec.js | 54 +++++++++++++++++++ 4 files changed, 136 insertions(+) create mode 100644 lib/errors/DocumentEntropyMismatchError.js create mode 100644 lib/errors/DocumentOwnerIdMismatchError.js diff --git a/lib/document/stateTransition/validation/data/validateDocumentsSTDataFactory.js b/lib/document/stateTransition/validation/data/validateDocumentsSTDataFactory.js index b25367d97..86ba690f1 100644 --- a/lib/document/stateTransition/validation/data/validateDocumentsSTDataFactory.js +++ b/lib/document/stateTransition/validation/data/validateDocumentsSTDataFactory.js @@ -6,6 +6,8 @@ const ValidationResult = require('../../../../validation/ValidationResult'); const DocumentAlreadyPresentError = require('../../../../errors/DocumentAlreadyPresentError'); const DocumentNotFoundError = require('../../../../errors/DocumentNotFoundError'); +const DocumentOwnerIdMismatchError = require('../../../../errors/DocumentOwnerIdMismatchError'); +const DocumentEntropyMismatchError = require('../../../../errors/DocumentEntropyMismatchError'); const InvalidDocumentRevisionError = require('../../../../errors/InvalidDocumentRevisionError'); const InvalidDocumentActionError = require('../../../errors/InvalidDocumentActionError'); @@ -75,6 +77,18 @@ function validateDocumentsSTDataFactory( break; } + if (fetchedDocument.getOwnerId() !== document.getOwnerId()) { + result.addError( + new DocumentOwnerIdMismatchError(document, fetchedDocument), + ); + } + + if (fetchedDocument.entropy !== document.entropy) { + result.addError( + new DocumentEntropyMismatchError(document, fetchedDocument), + ); + } + if (document.getRevision() !== fetchedDocument.getRevision() + 1) { result.addError( new InvalidDocumentRevisionError(document, fetchedDocument), diff --git a/lib/errors/DocumentEntropyMismatchError.js b/lib/errors/DocumentEntropyMismatchError.js new file mode 100644 index 000000000..f5659c2d4 --- /dev/null +++ b/lib/errors/DocumentEntropyMismatchError.js @@ -0,0 +1,34 @@ +const ConsensusError = require('./ConsensusError'); + +class DocumentEntropyMismatchError extends ConsensusError { + /** + * @param {Document} document + * @param {Document} fetchedDocument + */ + constructor(document, fetchedDocument) { + super('Document entropy mismatch with previous versions'); + + this.document = document; + this.fetchedDocument = fetchedDocument; + } + + /** + * Get Document + * + * @return {Document} + */ + getDocument() { + return this.document; + } + + /** + * Get fetched Document + * + * @return {Document} + */ + getFetchedDocument() { + return this.fetchedDocument; + } +} + +module.exports = DocumentEntropyMismatchError; diff --git a/lib/errors/DocumentOwnerIdMismatchError.js b/lib/errors/DocumentOwnerIdMismatchError.js new file mode 100644 index 000000000..821f26a94 --- /dev/null +++ b/lib/errors/DocumentOwnerIdMismatchError.js @@ -0,0 +1,34 @@ +const ConsensusError = require('./ConsensusError'); + +class DocumentOwnerIdMismatchError extends ConsensusError { + /** + * @param {Document} document + * @param {Document} fetchedDocument + */ + constructor(document, fetchedDocument) { + super('Document owner id mismatch with previous versions'); + + this.document = document; + this.fetchedDocument = fetchedDocument; + } + + /** + * Get Document + * + * @return {Document} + */ + getDocument() { + return this.document; + } + + /** + * Get fetched Document + * + * @return {Document} + */ + getFetchedDocument() { + return this.fetchedDocument; + } +} + +module.exports = DocumentOwnerIdMismatchError; diff --git a/test/unit/document/stateTransition/data/validateDocumentsSTDataFactory.spec.js b/test/unit/document/stateTransition/data/validateDocumentsSTDataFactory.spec.js index 90b198f30..ff76d1899 100644 --- a/test/unit/document/stateTransition/data/validateDocumentsSTDataFactory.spec.js +++ b/test/unit/document/stateTransition/data/validateDocumentsSTDataFactory.spec.js @@ -20,6 +20,8 @@ const DocumentNotFoundError = require('../../../../../lib/errors/DocumentNotFoun const InvalidDocumentRevisionError = require('../../../../../lib/errors/InvalidDocumentRevisionError'); const ConsensusError = require('../../../../../lib/errors/ConsensusError'); const InvalidDocumentActionError = require('../../../../../lib/document/errors/InvalidDocumentActionError'); +const DocumentOwnerIdMismatchError = require('../../../../../lib/errors/DocumentOwnerIdMismatchError'); +const DocumentEntropyMismatchError = require('../../../../../lib/errors/DocumentEntropyMismatchError'); describe('validateDocumentsSTDataFactory', () => { let validateDocumentsSTData; @@ -170,6 +172,58 @@ describe('validateDocumentsSTDataFactory', () => { expect(executeDataTriggersMock).to.have.not.been.called(); }); + it('should return invalid result if Document with action "update" has mismatch of ownerId with previous revision', async () => { + documents[0].setAction(Document.ACTIONS.REPLACE); + + const fetchedDocument = new Document(documents[0].toJSON()); + fetchedDocument.revision -= 1; + fetchedDocument.ownerId = '5zcXZpTLWFwZjKjq3ME5KVavtZa9YUaZESVzrndehBhq'; + + fetchDocumentsMock.resolves([fetchedDocument]); + + const result = await validateDocumentsSTData(stateTransition); + + expectValidationError(result, DocumentOwnerIdMismatchError); + + const [error] = result.getErrors(); + + expect(error.getDocument()).to.equal(documents[0]); + expect(error.getFetchedDocument()).to.equal(fetchedDocument); + + expect(fetchAndValidateDataContractMock).to.have.been.calledOnceWithExactly(documents[0]); + + expect(fetchDocumentsMock).to.have.been.calledOnceWithExactly(documents); + + expect(validateDocumentsUniquenessByIndicesMock).to.have.not.been.called(); + expect(executeDataTriggersMock).to.have.not.been.called(); + }); + + it('should return invalid result if Document with action "update" has mismatch of entropy with previous revision', async () => { + documents[0].setAction(Document.ACTIONS.REPLACE); + + const fetchedDocument = new Document(documents[0].toJSON()); + fetchedDocument.revision -= 1; + fetchedDocument.entropy = '5zcXZpTLWFwZjKjq3ME5KVavtZa9YUaZESVzrndehBhq'; + + fetchDocumentsMock.resolves([fetchedDocument]); + + const result = await validateDocumentsSTData(stateTransition); + + expectValidationError(result, DocumentEntropyMismatchError); + + const [error] = result.getErrors(); + + expect(error.getDocument()).to.equal(documents[0]); + expect(error.getFetchedDocument()).to.equal(fetchedDocument); + + expect(fetchAndValidateDataContractMock).to.have.been.calledOnceWithExactly(documents[0]); + + expect(fetchDocumentsMock).to.have.been.calledOnceWithExactly(documents); + + expect(validateDocumentsUniquenessByIndicesMock).to.have.not.been.called(); + expect(executeDataTriggersMock).to.have.not.been.called(); + }); + it('should return invalid result if Document with action "delete" has wrong revision', async () => { documents[0].setData({}); documents[0].setAction(Document.ACTIONS.DELETE);