diff --git a/keyserver/src/utils/validation-utils.js b/keyserver/src/utils/validation-utils.js index 5c1f56490..a386201b7 100644 --- a/keyserver/src/utils/validation-utils.js +++ b/keyserver/src/utils/validation-utils.js @@ -1,187 +1,190 @@ // @flow import type { PolicyType } from 'lib/facts/policies.js'; import { hasMinCodeVersion } from 'lib/shared/version-utils.js'; import { ServerError } from 'lib/utils/errors.js'; import { tCookie, tPassword, tPlatform, tPlatformDetails, } from 'lib/utils/validation-utils.js'; import { fetchNotAcknowledgedPolicies } from '../fetchers/policy-acknowledgment-fetchers.js'; import { verifyClientSupported } from '../session/version.js'; import type { Viewer } from '../session/viewer.js'; async function validateInput(viewer: Viewer, inputValidator: *, input: *) { if (!viewer.isSocket) { await checkClientSupported(viewer, inputValidator, input); } checkInputValidator(inputValidator, input); } function checkInputValidator(inputValidator: *, input: *) { if (!inputValidator || inputValidator.is(input)) { return; } const error = new ServerError('invalid_parameters'); error.sanitizedInput = input ? sanitizeInput(inputValidator, input) : null; throw error; } async function checkClientSupported( viewer: Viewer, inputValidator: *, input: *, ) { let platformDetails; if (inputValidator) { platformDetails = findFirstInputMatchingValidator( inputValidator, tPlatformDetails, input, ); } if (!platformDetails && inputValidator) { const platform = findFirstInputMatchingValidator( inputValidator, tPlatform, input, ); if (platform) { platformDetails = { platform }; } } if (!platformDetails) { ({ platformDetails } = viewer); } await verifyClientSupported(viewer, platformDetails); } const redactedString = '********'; const redactedTypes = [tPassword, tCookie]; -function sanitizeInput(inputValidator: *, input: *) { +function sanitizeInput(inputValidator: any, input: any): any { if (!inputValidator) { return input; } if (redactedTypes.includes(inputValidator) && typeof input === 'string') { return redactedString; } if ( inputValidator.meta.kind === 'maybe' && redactedTypes.includes(inputValidator.meta.type) && typeof input === 'string' ) { return redactedString; } if ( inputValidator.meta.kind !== 'interface' || typeof input !== 'object' || !input ) { return input; } const result = {}; for (const key in input) { const value = input[key]; const validator = inputValidator.meta.props[key]; result[key] = sanitizeInput(validator, value); } return result; } function findFirstInputMatchingValidator( wholeInputValidator: *, inputValidatorToMatch: *, input: *, ): any { if (!wholeInputValidator || input === null || input === undefined) { return null; } if ( wholeInputValidator === inputValidatorToMatch && wholeInputValidator.is(input) ) { return input; } if (wholeInputValidator.meta.kind === 'maybe') { return findFirstInputMatchingValidator( wholeInputValidator.meta.type, inputValidatorToMatch, input, ); } if ( wholeInputValidator.meta.kind === 'interface' && typeof input === 'object' ) { for (const key in input) { const value = input[key]; const validator = wholeInputValidator.meta.props[key]; const innerResult = findFirstInputMatchingValidator( validator, inputValidatorToMatch, value, ); if (innerResult) { return innerResult; } } } if (wholeInputValidator.meta.kind === 'union') { for (const validator of wholeInputValidator.meta.types) { if (validator.is(input)) { return findFirstInputMatchingValidator( validator, inputValidatorToMatch, input, ); } } } if (wholeInputValidator.meta.kind === 'list' && Array.isArray(input)) { const validator = wholeInputValidator.meta.type; for (const value of input) { const innerResult = findFirstInputMatchingValidator( validator, inputValidatorToMatch, value, ); if (innerResult) { return innerResult; } } } return null; } async function policiesValidator( viewer: Viewer, policies: $ReadOnlyArray, ) { if (!policies.length) { return; } if (!hasMinCodeVersion(viewer.platformDetails, 181)) { return; } const notAcknowledgedPolicies = await fetchNotAcknowledgedPolicies( viewer.id, policies, ); if (notAcknowledgedPolicies.length) { throw new ServerError('policies_not_accepted', { notAcknowledgedPolicies, }); } } export { validateInput, checkInputValidator, + redactedString, + sanitizeInput, + findFirstInputMatchingValidator, checkClientSupported, policiesValidator, }; diff --git a/keyserver/src/utils/validation-utils.test.js b/keyserver/src/utils/validation-utils.test.js new file mode 100644 index 000000000..2563342bb --- /dev/null +++ b/keyserver/src/utils/validation-utils.test.js @@ -0,0 +1,27 @@ +// @flow + +import t from 'tcomb'; + +import { tPassword, tShape } from 'lib/utils/validation-utils.js'; + +import { sanitizeInput, redactedString } from './validation-utils.js'; + +describe('sanitization', () => { + it('should redact a string', () => { + expect(sanitizeInput(tPassword, 'password')).toStrictEqual(redactedString); + }); + + it('should redact a string inside an object', () => { + const validator = tShape({ password: tPassword }); + const object = { password: 'password' }; + const redacted = { password: redactedString }; + expect(sanitizeInput(validator, object)).toStrictEqual(redacted); + }); + + it('should redact an optional string', () => { + const validator = tShape({ password: t.maybe(tPassword) }); + const object = { password: 'password' }; + const redacted = { password: redactedString }; + expect(sanitizeInput(validator, object)).toStrictEqual(redacted); + }); +});