Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hide password values from raw HTTP logs. #4066

Merged
Merged
10 changes: 6 additions & 4 deletions lib/http/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 ? '<REDACTED>' : 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));
}
Expand Down
9 changes: 5 additions & 4 deletions lib/transport/selenium-webdriver/method-mappings.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
describe('value redaction in setPassword', function() {
test('test setPassword', async browser => {
browser
.setPassword('#weblogin', 'password')
.setValue('#weblogin', 'simpletext');
});
});
5 changes: 3 additions & 2 deletions test/src/api/commands/element/testSetPassword.js
Original file line number Diff line number Diff line change
@@ -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');

Expand All @@ -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',
Expand Down
104 changes: 104 additions & 0 deletions test/src/api/commands/element/testSetPasswordReport.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
Loading