Skip to content

Commit

Permalink
feat: mask values typed into input[type=password] in a reporter (#6191)
Browse files Browse the repository at this point in the history
* feat: mask values typed into input[type=password] in a reporter

* refactor code and ts definitions

* address suggestions

* refactor _getActiveElement method
  • Loading branch information
wentwrong authored May 12, 2021
1 parent 024a002 commit 3d81459
Show file tree
Hide file tree
Showing 17 changed files with 377 additions and 36 deletions.
11 changes: 11 additions & 0 deletions src/client/driver/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ import DriverStatus from './status';
import generateId from './generate-id';
import ChildIframeDriverLink from './driver-link/iframe/child';

import { createReplicator, SelectorNodeTransform } from './command-executors/client-functions/replicator';

import executeActionCommand from './command-executors/execute-action';
import executeManipulationCommand from './command-executors/browser-manipulation';
import executeNavigateToCommand from './command-executors/execute-navigate-to';
Expand Down Expand Up @@ -200,11 +202,14 @@ export default class Driver extends serviceUtils.EventEmitter {
hammerhead.on(hammerhead.EVENTS.windowOpened, e => this._onChildWindowOpened(e));

this.setCustomCommandHandlers(COMMAND_TYPE.unlockPage, () => this._unlockPageAfterTestIsDone());
this.setCustomCommandHandlers(COMMAND_TYPE.getActiveElement, () => this._getActiveElement());

// NOTE: initiate the child links restoring process before the window is reloaded
listeners.addInternalEventBeforeListener(window, ['beforeunload'], () => {
this._sendStartToRestoreCommand();
});

this.replicator = createReplicator([ new SelectorNodeTransform() ]);
}

_isOpenedInIframe () {
Expand Down Expand Up @@ -268,6 +273,12 @@ export default class Driver extends serviceUtils.EventEmitter {
return Promise.resolve();
}

async _getActiveElement () {
const activeElement = domUtils.getActiveElement();

return this.replicator.encode(activeElement);
}

_failIfClientCodeExecutionIsInterrupted () {
// NOTE: ClientFunction should be used primarily for observation. We raise
// an error if the page was reloaded during ClientFunction execution.
Expand Down
4 changes: 0 additions & 4 deletions src/configuration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,4 @@ interface QuarantineOptionValue {
passCount?: number;
}

interface CompilerOptions {
[key: string]: object;
}

type OptionValue = undefined | null | string | boolean | number | string[] | Function | { [key: string]: any } | ScreenshotOptionValue | QuarantineOptionValue | CompilerOptions;
16 changes: 16 additions & 0 deletions src/reporter/command/command-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { isEmpty } from 'lodash';
import { ExecuteSelectorCommand, ExecuteClientFunctionCommand } from '../../test-run/commands/observation';
import {
NavigateToCommand,
PressKeyCommand,
SetNativeDialogHandlerCommand,
TypeTextCommand,
UseRoleCommand
} from '../../test-run/commands/actions';

Expand All @@ -23,6 +25,8 @@ import {
} from '../../test-run/commands/options';


const CONFIDENTIAL_INFO_PLACEHOLDER = '********';

function isCommandOptions (obj: object): boolean {
return obj instanceof ActionOptions || obj instanceof ResizeToFitDeviceOptions || obj instanceof AssertionOptions;
}
Expand Down Expand Up @@ -53,9 +57,21 @@ export class CommandFormatter {
else
this._assignProperties(this._command, formattedCommand);

this._maskConfidentialInfo(formattedCommand);

return formattedCommand;
}

private _maskConfidentialInfo (command: FormattedCommand): void {
if (!(command.options as any)?.confidential)
return;

if (this._command instanceof TypeTextCommand)
command.text = CONFIDENTIAL_INFO_PLACEHOLDER;
else if (this._command instanceof PressKeyCommand)
command.keys = CONFIDENTIAL_INFO_PLACEHOLDER;
}

private _getElementByPropertyName (propertyName: string): HTMLElement {
this._ensureSelectorElements();

Expand Down
7 changes: 6 additions & 1 deletion src/test-run/commands/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
ClickOptions,
MouseOptions,
TypeOptions,
PressOptions,
DragToElementOptions,
OffsetOptions
} from './options';
Expand Down Expand Up @@ -60,6 +61,10 @@ function initDragToElementOptions (name, val) {
return new DragToElementOptions(val, true);
}

function initPressOptions (name, val) {
return new PressOptions(val, true);
}

function initDialogHandler (name, val, { skipVisibilityCheck, testRun }) {
let fn;

Expand Down Expand Up @@ -325,7 +330,7 @@ export class PressKeyCommand extends CommandBase {
_getAssignableProperties () {
return [
{ name: 'keys', type: nonEmptyStringArgument, required: true },
{ name: 'options', type: actionOptions, init: initActionOptions, required: true }
{ name: 'options', type: actionOptions, init: initPressOptions, required: true }
];
}
}
Expand Down
25 changes: 22 additions & 3 deletions src/test-run/commands/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,16 +186,18 @@ export class TypeOptions extends ClickOptions {
constructor (obj, validate) {
super();

this.replace = false;
this.paste = false;
this.replace = false;
this.paste = false;
this.confidential = void 0;

this._assignFrom(obj, validate);
}

_getAssignableProperties () {
return super._getAssignableProperties().concat([
{ name: 'replace', type: booleanOption },
{ name: 'paste', type: booleanOption }
{ name: 'paste', type: booleanOption },
{ name: 'confidential', type: booleanOption }
]);
}
}
Expand Down Expand Up @@ -254,3 +256,20 @@ export class AssertionOptions extends Assignable {
];
}
}

// Press
export class PressOptions extends ActionOptions {
constructor (obj, validate) {
super();

this.confidential = void 0;

this._assignFrom(obj, validate);
}

_getAssignableProperties () {
return super._getAssignableProperties().concat([
{ name: 'confidential', type: booleanOption }
]);
}
}
5 changes: 5 additions & 0 deletions src/test-run/commands/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@ export class UnlockPageCommand {
}
}

export class GetActiveElementCommand {
constructor () {
this.type = TYPE.getActiveElement;
}
}
1 change: 1 addition & 0 deletions src/test-run/commands/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default {
setNativeDialogHandler: 'set-native-dialog-handler',
getNativeDialogHistory: 'get-native-dialog-history',
getBrowserConsoleMessages: 'get-browser-console-messages',
getActiveElement: 'get-active-element',
setTestSpeed: 'set-test-speed',
setPageLoadTimeout: 'set-page-load-timeout',
debug: 'debug',
Expand Down
34 changes: 34 additions & 0 deletions src/test-run/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import PHASE from './phase';
import CLIENT_MESSAGES from './client-messages';
import COMMAND_TYPE from './commands/type';
import delay from '../utils/delay';
import isPasswordInput from '../utils/is-password-input';
import testRunMarker from './marker-symbol';
import testRunTracker from '../api/test-run-tracker';
import ROLE_PHASE from '../role/phase';
Expand Down Expand Up @@ -53,6 +54,8 @@ import { RUNTIME_ERRORS, TEST_RUN_ERRORS } from '../errors/types';
import processTestFnError from '../errors/process-test-fn-error';
import RequestHookMethodNames from '../api/request-hooks/hook-method-names';

import { createReplicator, SelectorNodeTransform } from '../client-functions/replicator';

const lazyRequire = require('import-lazy')(require);
const SessionController = lazyRequire('./session-controller');
const ObservedCallsitesStorage = lazyRequire('./observed-callsites-storage');
Expand Down Expand Up @@ -151,6 +154,8 @@ export default class TestRun extends AsyncEventEmitter {
this.observedCallsites = new ObservedCallsitesStorage();
this.compilerService = compilerService;

this.replicator = createReplicator([ new SelectorNodeTransform() ]);

this._addInjectables();
this._initRequestHooks();
}
Expand Down Expand Up @@ -691,6 +696,33 @@ export default class TestRun extends AsyncEventEmitter {
command.generateScreenshotMark();
}

async _adjustCommandOptions (command) {
if (command.options?.confidential !== void 0)
return;

if (command.type === COMMAND_TYPE.typeText) {
const result = await this.executeCommand(command.selector);

if (!result)
return;

const node = this.replicator.decode(result);

command.options.confidential = isPasswordInput(node);
}

else if (command.type === COMMAND_TYPE.pressKey) {
const result = await this.executeCommand(new serviceCommands.GetActiveElementCommand());

if (!result)
return;

const node = this.replicator.decode(result);

command.options.confidential = isPasswordInput(node);
}
}

async _setBreakpointIfNecessary (command, callsite) {
if (!this.disableDebugBreakpoints && this.debugging && canSetDebuggerBreakpointBeforeCommand(command))
await this._enqueueSetBreakpointCommand(callsite);
Expand All @@ -703,6 +735,8 @@ export default class TestRun extends AsyncEventEmitter {
let error = null;
let result = null;

await this._adjustCommandOptions(command);

await this.emitActionEvent('action-start', actionArgs);

const start = new Date();
Expand Down
6 changes: 5 additions & 1 deletion src/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"include": [ "../@types", "./"],
"include": [
"../@types",
"../ts-defs-src",
"./"
],
"exclude": ["./client"],
"compilerOptions": {
"outDir": "../lib",
Expand Down
6 changes: 6 additions & 0 deletions src/utils/is-password-input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default function isPasswordInput (node?: NodeSnapshot): boolean {
if (!node)
return false;

return node.tagName === 'input' && node.attributes?.type === 'password';
}
1 change: 1 addition & 0 deletions test/functional/fixtures/reporter/pages/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@
<div id="contenteditable" contenteditable="true"></div>
<input type="file" id="file"/>
<iframe id="iframe" src="./index.html"></iframe>
<input type="password" id="password-input"></input>
</body>
</html>
8 changes: 6 additions & 2 deletions test/functional/fixtures/reporter/reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ function generateReporter (log, options = {}) {
emitOnStart = true,
emitOnDone = true,
includeBrowserInfo = false,
includeTestInfo = false
includeTestInfo = false,
includeCommandInfo = false
} = options;

return function () {
return Object.assign({}, baseReport, {
async reportTestActionStart (name, { browser, test, fixture }) {
async reportTestActionStart (name, { browser, test, fixture, command }) {
if (!emitOnStart)
return;

Expand All @@ -45,6 +46,9 @@ function generateReporter (log, options = {}) {
}
}

if (includeCommandInfo)
item.command = command;

log.push(item);
},

Expand Down
Loading

0 comments on commit 3d81459

Please sign in to comment.