diff --git a/lib/validation/custom-zschema-validators.js b/lib/validation/custom-zschema-validators.js index 7e51439..ba93a9b 100644 --- a/lib/validation/custom-zschema-validators.js +++ b/lib/validation/custom-zschema-validators.js @@ -205,6 +205,81 @@ function shouldSkipValidate (options, errors) { }); } +function isPropertyWriteOnly (xMsMutability) { + return xMsMutability && xMsMutability.indexOf('read') === -1 && (xMsMutability.indexOf('create') != -1 || xMsMutability.indexOf('update') != -1); +} + +function checkSecretPropertyInResponse (validateOptions, schema, json, report) { + if (shouldSkipValidate(validateOptions, ['SECRET_PROPERTY'])) { + return + } + + var isResponse = validateOptions && validateOptions.isResponse + var xMsSecret = schema && schema['x-ms-secret'] + + if (isResponse && schema && xMsSecret && json !== undefined) { + let errorMessage = 'Secret property `"{0}": '; + + if (schema && schema.type === 'string' && typeof json === 'string') { + errorMessage += '"{1}"'; + } else { + errorMessage += '{1}'; + } + let propertyName = ''; + + if (schema.title && typeof schema.title === 'string') { + try { + let result = JSON.parse(schema.title); + + if (Array.isArray(result.path) && result.path.length) { + propertyName = result.path[result.path.length - 1]; + } + } catch (err) { + // do nothing + } + } + errorMessage += '`, cannot be sent in the response.'; + report.addCustomError('SECRET_PROPERTY', errorMessage, [propertyName, json], null, schema); + } +} + +function checkWriteOnlyPropertyInResponse (validateOptions, schema, json, report) { + // Check if there's a write-only property in the response. + // Write-only property definition: Property with 'x-ms-mutability' that does NOT have ['read'] and only has 'create' or 'update' or both + + if (shouldSkipValidate(validateOptions, ['WRITEONLY_PROPERTY_NOT_ALLOWED_IN_RESPONSE'])) { + return + } + + var isResponse = validateOptions && validateOptions.isResponse + var xMsMutability = schema && schema['x-ms-mutability'] + + if (isResponse && schema && isPropertyWriteOnly(xMsMutability) && json !== undefined) { + let errorMessage = 'Write-only property `"{0}": '; + + if (schema && schema.type === 'string' && typeof json === 'string') { + errorMessage += '"{1}"'; + } else { + errorMessage += '{1}'; + } + let propertyName = ''; + + if (schema.title && typeof schema.title === 'string') { + try { + let result = JSON.parse(schema.title); + + if (Array.isArray(result.path) && result.path.length) { + propertyName = result.path[result.path.length - 1]; + } + } catch (err) { + // do nothing + } + } + errorMessage += '`, is not allowed in the response.'; + report.addCustomError('WRITEONLY_PROPERTY_NOT_ALLOWED_IN_RESPONSE', errorMessage, [propertyName, json], null, schema); + } +} + function readOnlyValidator (report, schema, json) { // http://json-schema.org/latest/json-schema-validation.html#rfc.section.10.3 if (shouldSkipValidate(this.validateOptions, ['READONLY_PROPERTY_NOT_ALLOWED_IN_REQUEST'])) { @@ -248,45 +323,8 @@ function readOnlyValidator (report, schema, json) { } function customValidatorFn (report, schema, json) { - if (shouldSkipValidate(this.validateOptions, ['SECRET_PROPERTY'])) { - return - } - - var isResponse = this.validateOptions && this.validateOptions.isResponse - var xMsSecret = schema && schema['x-ms-secret'] - - if (isResponse && schema && xMsSecret && json !== undefined) { - let errorMessage = 'Secret property `"{0}": ' - - if (schema && schema.type === 'string' && typeof json === 'string') { - errorMessage += '"{1}"' - } else { - errorMessage += '{1}' - } - - let propertyName = '' - - if (schema.title && typeof schema.title === 'string') { - try { - let result = JSON.parse(schema.title) - - if (Array.isArray(result.path) && result.path.length) { - propertyName = result.path[result.path.length - 1] - } - } catch (err) { - // do nothing - } - } - - errorMessage += '`, cannot be sent in the response.' - report.addCustomError( - 'SECRET_PROPERTY', - errorMessage, - [propertyName, json], - null, - schema - ) - } + checkWriteOnlyPropertyInResponse(this.validateOptions, schema, json, report); + checkSecretPropertyInResponse(this.validateOptions, schema, json, report); } diff --git a/package.json b/package.json index 4b7484b..9ccef27 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "yasway", - "version": "1.8.5", + "version": "1.8.6", "description": "A library that simplifies Swagger integrations.", "main": "index.js", "types": "index.d.ts", diff --git a/test/browser/documents/2.0/swagger.yaml b/test/browser/documents/2.0/swagger.yaml index afa959b..95582df 100644 --- a/test/browser/documents/2.0/swagger.yaml +++ b/test/browser/documents/2.0/swagger.yaml @@ -680,6 +680,18 @@ definitions: - "available" - "pending" - "sold" + secret: + type: "string" + x-ms-secret: ["secret"] + writeOnly: + type: "string" + x-ms-mutability: ["create", "update"] + createOnly: + type: "string" + x-ms-mutability: ["create"] + updateOnly: + type: "string" + x-ms-mutability: ["update"] type: "object" xml: name: "Pet" diff --git a/test/test-response.js b/test/test-response.js index f881915..9ffdbe8 100644 --- a/test/test-response.js +++ b/test/test-response.js @@ -129,6 +129,30 @@ describe('Response', function () { photoUrls: [] }; + var writeOnlyPet = { + name: 'Test Pet', + photoUrls: [], + writeOnly: 'writeonly' + }; + + var createOnlyPet = { + name: 'Test Pet', + photoUrls: [], + createOnly: 'createonly' + } + + var updateOnlyPet = { + name: 'Test Pet', + photoUrls: [], + updateOnly: 'updateonly' + } + + var secretPet = { + name: 'Test Pet', + photoUrls: [], + secret: 'password' + } + describe('validate Content-Type', function () { describe('operation level produces', function () { var cSway; @@ -624,6 +648,150 @@ describe('Response', function () { }) .then(done, done); }); + + it('Test if response has secret property marked with x-ms-secret', function (done) { + var cSwaggerDoc = _.cloneDeep(helpers.swaggerDoc); + + Sway.create({ + definition: cSwaggerDoc + }) + .then(function (api) { + var results = api.getOperation('/pet/{petId}', 'get').validateResponse({ + body: secretPet, + encoding: 'utf-8', + headers: { + 'content-type': 'application/json' + }, + statusCode: 200 + }); + + assert.deepEqual(results.errors, [ + { + code: 'INVALID_RESPONSE_BODY', + errors: [ + { + code: 'SECRET_PROPERTY', + message: 'Secret property `"": "password"`, cannot be sent in the response.', + params: ['', 'password'], + path: ['secret'] + } + ], + message: 'Invalid body: Secret property `"": "password"`, cannot be sent in the response.', + path: [] + } + ]); + assert.equal(results.warnings.length, 0); + }) + .then(done, done); + }); + + it('Test if response has WRITE only property marked with x-ms-mutability', function (done) { + var cSwaggerDoc = _.cloneDeep(helpers.swaggerDoc); + + Sway.create({ + definition: cSwaggerDoc + }) + .then(function (api) { + var results = api.getOperation('/pet/{petId}', 'get').validateResponse({ + body: writeOnlyPet, + encoding: 'utf-8', + headers: { + 'content-type': 'application/json' + }, + statusCode: 200 + }); + + assert.deepEqual(results.errors, [ + { + code: 'INVALID_RESPONSE_BODY', + errors: [ + { + code: 'WRITEONLY_PROPERTY_NOT_ALLOWED_IN_RESPONSE', + message: 'Write-only property `"": "writeonly"`, is not allowed in the response.', + params: ['', 'writeonly'], + path: ['writeOnly'] + } + ], + message: 'Invalid body: Write-only property `"": "writeonly"`, is not allowed in the response.', + path: [] + } + ]); + assert.equal(results.warnings.length, 0); + }) + .then(done, done); + }); + + it('Test if response has CREATE only property marked with x-ms-mutability', function (done) { + var cSwaggerDoc = _.cloneDeep(helpers.swaggerDoc); + + Sway.create({ + definition: cSwaggerDoc + }) + .then(function (api) { + var results = api.getOperation('/pet/{petId}', 'get').validateResponse({ + body: createOnlyPet, + encoding: 'utf-8', + headers: { + 'content-type': 'application/json' + }, + statusCode: 200 + }); + + assert.deepEqual(results.errors, [ + { + code: 'INVALID_RESPONSE_BODY', + errors: [ + { + code: 'WRITEONLY_PROPERTY_NOT_ALLOWED_IN_RESPONSE', + message: 'Write-only property `"": "createonly"`, is not allowed in the response.', + params: ['', 'createonly'], + path: ['createOnly'] + } + ], + message: 'Invalid body: Write-only property `"": "createonly"`, is not allowed in the response.', + path: [] + } + ]); + assert.equal(results.warnings.length, 0); + }) + .then(done, done); + }); + + it('Test if response has UPDATE only property marked with x-ms-mutability', function (done) { + var cSwaggerDoc = _.cloneDeep(helpers.swaggerDoc); + + Sway.create({ + definition: cSwaggerDoc + }) + .then(function (api) { + var results = api.getOperation('/pet/{petId}', 'get').validateResponse({ + body: updateOnlyPet, + encoding: 'utf-8', + headers: { + 'content-type': 'application/json' + }, + statusCode: 200 + }); + + assert.deepEqual(results.errors, [ + { + code: 'INVALID_RESPONSE_BODY', + errors: [ + { + code: 'WRITEONLY_PROPERTY_NOT_ALLOWED_IN_RESPONSE', + message: 'Write-only property `"": "updateonly"`, is not allowed in the response.', + params: ['', 'updateonly'], + path: ['updateOnly'] + } + ], + message: 'Invalid body: Write-only property `"": "updateonly"`, is not allowed in the response.', + path: [] + } + ]); + assert.equal(results.warnings.length, 0); + }) + .then(done, done); + }); }); }); });