diff --git a/lib/http/request.js b/lib/http/request.js index 91b559943d..37f4fb00e0 100644 --- a/lib/http/request.js +++ b/lib/http/request.js @@ -3,6 +3,7 @@ const http = require('http'); const https = require('https'); const dns = require('dns'); const path = require('path'); +const {Key} = require('selenium-webdriver'); const HttpUtil = require('./http.js'); const HttpOptions = require('./options.js'); @@ -126,7 +127,6 @@ class HttpRequest extends EventEmitter { this.reqOptions = this.createHttpOptions(options); this.hostname = Formatter.formatHostname(this.reqOptions.host, this.reqOptions.port, this.use_ssl); this.retryAttempts = this.httpOpts.retry_attempts; - this.redact = options.redact || false; return this; } @@ -253,13 +253,15 @@ class HttpRequest extends EventEmitter { return arg; }); + } else if (this.reqOptions.method === 'POST' && this.reqOptions.path.endsWith('/value') && params.text.startsWith(Key.NULL)) { + params.text = '*******'; + params.value = '*******'.split(''); } const content = ` Request ${[this.reqOptions.method, this.hostname + this.reqOptions.path, retryStr + ' '].join(' ')}`; - const paramsStr = this.redact ? '' : params; - Logger.request(content, paramsStr); - Logger.info(content, paramsStr); + Logger.request(content, params); + Logger.info(content, params); this.httpRequest.on('error', err => this.onRequestError(err)); } diff --git a/lib/transport/selenium-webdriver/method-mappings.js b/lib/transport/selenium-webdriver/method-mappings.js index e91462fef5..bb9155735f 100644 --- a/lib/transport/selenium-webdriver/method-mappings.js +++ b/lib/transport/selenium-webdriver/method-mappings.js @@ -1,4 +1,4 @@ -const {WebElement, WebDriver, Origin, By, until, Condition} = require('selenium-webdriver'); +const {WebElement, WebDriver, Origin, By, until, Condition, Key} = require('selenium-webdriver'); const {Locator} = require('../../element'); const NightwatchLocator = require('../../element/locator-factory.js'); const {isString} = require('../../utils'); @@ -590,9 +590,10 @@ module.exports = class MethodMappings { return null; }, - setElementValueRedacted(...args) { - // FIXME: redact password in verbose HTTP logs, it's only redacted in the command logs - return this.methods.session.setElementValue.call(this, ...args); + setElementValueRedacted(webElementOrId, value) { + const modifiedValue = [Key.NULL].concat(value); + + return this.methods.session.setElementValue.call(this, webElementOrId, modifiedValue); }, async setElementValue(webElementOrId, value) { diff --git a/test/sampletests/passwordValueRedacted/passwordValueRedacted.js b/test/sampletests/passwordValueRedacted/passwordValueRedacted.js new file mode 100644 index 0000000000..cd8d71a97c --- /dev/null +++ b/test/sampletests/passwordValueRedacted/passwordValueRedacted.js @@ -0,0 +1,7 @@ +describe('value redaction in setPassword', function() { + test('test setPassword', async browser => { + browser + .setPassword('#weblogin', 'password') + .setValue('#weblogin', 'simpletext'); + }); +}); diff --git a/test/src/api/commands/element/testSetPassword.js b/test/src/api/commands/element/testSetPassword.js index 176b27aa7f..24c469b8b6 100644 --- a/test/src/api/commands/element/testSetPassword.js +++ b/test/src/api/commands/element/testSetPassword.js @@ -1,4 +1,5 @@ const assert = require('assert'); +const {Key} = require('selenium-webdriver'); const MockServer = require('../../../../lib/mockserver.js'); const CommandGlobals = require('../../../../lib/globals/commands.js'); @@ -17,8 +18,8 @@ describe('setPassword', function() { url: '/wd/hub/session/1352110219202/element/0/value', method: 'POST', postdata: { - text: 'password', - value: ['p', 'a', 's', 's', 'w', 'o', 'r', 'd'] + text: Key.NULL + 'password', + value: [Key.NULL, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'] }, response: { sessionId: '1352110219202', diff --git a/test/src/api/commands/element/testSetPasswordReport.js b/test/src/api/commands/element/testSetPasswordReport.js new file mode 100644 index 0000000000..68a95e8bea --- /dev/null +++ b/test/src/api/commands/element/testSetPasswordReport.js @@ -0,0 +1,104 @@ +const assert = require('assert'); +const path = require('path'); +const {Key} = require('selenium-webdriver'); +const common = require('../../../../common.js'); +const Mocks = require('../../../../lib/command-mocks.js'); +const {settings} = common; +const NightwatchClient = common.require('index.js'); +const MockServer = require('../../../../lib/mockserver.js'); + +describe('setPassword report check', function() { + before(function(done) { + this.server = MockServer.init(); + this.server.on('listening', () => done()); + }); + + after(function(done) { + this.server.close(function() { + done(); + }); + }); + + it('client.setPassword() value redacted in rawHttpOutput', async function() { + let sendKeysPasswordMockCalled = false; + let sendKeysNormalMockCalled = false; + let globalReporterCalled = false; + + Mocks.createNewW3CSession({ + testName: 'Actions API demo tests' + }); + + MockServer.addMock({ + url: '/session/13521-10219-202/element/5cc459b8-36a8-3042-8b4a-258883ea642b/clear', + method: 'POST', + statusCode: 200, + response: { + value: null + }, + times: 2 + }); + + MockServer.addMock({ + url: '/session/13521-10219-202/element/5cc459b8-36a8-3042-8b4a-258883ea642b/value', + method: 'POST', + postdata: {text: Key.NULL + 'password', value: [Key.NULL, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd']}, + response: { + value: null + }, + onRequest: () => { + sendKeysPasswordMockCalled = true; + } + }, true); + + MockServer.addMock({ + url: '/session/13521-10219-202/element/5cc459b8-36a8-3042-8b4a-258883ea642b/value', + method: 'POST', + postdata: {text: 'simpletext', value: ['s', 'i', 'm', 'p', 'l', 'e', 't', 'e', 'x', 't']}, + response: { + value: null + }, + onRequest: () => { + sendKeysNormalMockCalled = true; + } + }, true); + + const testsPath = [ + path.join(__dirname, '../../../../sampletests/passwordValueRedacted/passwordValueRedacted.js') + ]; + + const globals = { + reporter(results) { + globalReporterCalled = true; + + assert.strictEqual(sendKeysPasswordMockCalled, true); + assert.strictEqual(sendKeysNormalMockCalled, true); + assert.strictEqual(results.errmessages.length, 0); + + const rawHttpOutput = results.modules.passwordValueRedacted.rawHttpOutput; + const requests = rawHttpOutput + .filter((req) => { + return req[1].includes('element/5cc459b8-36a8-3042-8b4a-258883ea642b/value') && + req[1].includes('Request POST'); + }); + + assert.strictEqual(requests.length, 2); + + // First request (setPassword) should contain redacted value + assert.strictEqual(requests[0][2].includes('password'), false); + assert.strictEqual(requests[0][2].includes('*******'), true); + + // Second request (setValue) should NOT contain redacted value + assert.strictEqual(requests[1][2].includes('simpletext'), true); + assert.strictEqual(requests[1][2].includes('*******'), false); + } + }; + + await NightwatchClient.runTests(testsPath, settings({ + globals, + output_folder: 'output', + selenium_host: null + })); + + assert.strictEqual(globalReporterCalled, true); + }); +});