Page MenuHomePhabricator

D7488.id25273.diff
No OneTemporary

D7488.id25273.diff

diff --git a/keyserver/src/utils/validation-utils.js b/keyserver/src/utils/validation-utils.js
--- a/keyserver/src/utils/validation-utils.js
+++ b/keyserver/src/utils/validation-utils.js
@@ -1,5 +1,10 @@
// @flow
+import _map from 'lodash/fp/map.js';
+import _mapKeys from 'lodash/fp/mapKeys.js';
+import _mapValues from 'lodash/fp/mapValues.js';
+import type { TType } from 'tcomb';
+
import type { PolicyType } from 'lib/facts/policies.js';
import { hasMinCodeVersion } from 'lib/shared/version-utils.js';
import { ServerError } from 'lib/utils/errors.js';
@@ -14,14 +19,18 @@
import { verifyClientSupported } from '../session/version.js';
import type { Viewer } from '../session/viewer.js';
-async function validateInput(viewer: Viewer, inputValidator: *, input: *) {
+async function validateInput(
+ viewer: Viewer,
+ inputValidator: ?TType<mixed>,
+ input: mixed,
+) {
if (!viewer.isSocket) {
await checkClientSupported(viewer, inputValidator, input);
}
checkInputValidator(inputValidator, input);
}
-function checkInputValidator(inputValidator: *, input: *) {
+function checkInputValidator(inputValidator: ?TType<mixed>, input: mixed) {
if (!inputValidator || inputValidator.is(input)) {
return;
}
@@ -32,8 +41,8 @@
async function checkClientSupported(
viewer: Viewer,
- inputValidator: *,
- input: *,
+ inputValidator: ?TType<mixed>,
+ input: mixed,
) {
let platformDetails;
if (inputValidator) {
@@ -61,34 +70,13 @@
const redactedString = '********';
const redactedTypes = [tPassword, tCookie];
-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 sanitizeInput<T>(inputValidator: TType<T>, input: T): T {
+ return convertObject(
+ inputValidator,
+ input,
+ redactedTypes,
+ () => redactedString,
+ );
}
function findFirstInputMatchingValidator(
@@ -156,6 +144,83 @@
return null;
}
+function convertObject<T, I: any>(
+ validator: TType<I>,
+ input: I,
+ typesToConvert: $ReadOnlyArray<TType<T>>,
+ conversionFunction: T => T,
+): I {
+ if (input === null || input === undefined) {
+ return input;
+ }
+
+ if (typesToConvert.includes(validator) && validator.is(input)) {
+ // At this point generics should be equal (T=I) so this is safe
+ // $FlowExpectedError[incompatible-return]
+ // $FlowExpectedError[incompatible-call]
+ return conversionFunction(input);
+ }
+ if (validator.meta.kind === 'maybe') {
+ return convertObject(
+ validator.meta.type,
+ input,
+ typesToConvert,
+ conversionFunction,
+ );
+ }
+ if (validator.meta.kind === 'interface' && typeof input === 'object') {
+ const result = {};
+ for (const key in input) {
+ const innerValidator = validator.meta.props[key];
+ result[key] = convertObject(
+ innerValidator,
+ input[key],
+ typesToConvert,
+ conversionFunction,
+ );
+ }
+ // convertObject doesn't change any inner types
+ // $FlowExpectedError[incompatible-return]
+ return result;
+ }
+ if (validator.meta.kind === 'union') {
+ for (const innerValidator of validator.meta.types) {
+ if (innerValidator.is(input)) {
+ return convertObject(
+ innerValidator,
+ input,
+ typesToConvert,
+ conversionFunction,
+ );
+ }
+ }
+ return input;
+ }
+ if (validator.meta.kind === 'list' && Array.isArray(input)) {
+ const innerValidator = validator.meta.type;
+ return _map(value =>
+ convertObject(innerValidator, value, typesToConvert, conversionFunction),
+ )(input);
+ }
+ if (validator.meta.kind === 'dict' && typeof input === 'object') {
+ const domainValidator = validator.meta.domain;
+ const codomainValidator = validator.meta.codomain;
+ if (typesToConvert.includes(domainValidator)) {
+ input = _mapKeys(key => conversionFunction(key))(input);
+ }
+ return _mapValues(value =>
+ convertObject(
+ codomainValidator,
+ value,
+ typesToConvert,
+ conversionFunction,
+ ),
+ )(input);
+ }
+
+ return input;
+}
+
async function policiesValidator(
viewer: Viewer,
policies: $ReadOnlyArray<PolicyType>,
@@ -186,5 +251,6 @@
sanitizeInput,
findFirstInputMatchingValidator,
checkClientSupported,
+ convertObject,
policiesValidator,
};
diff --git a/keyserver/src/utils/validation-utils.test.js b/keyserver/src/utils/validation-utils.test.js
--- a/keyserver/src/utils/validation-utils.test.js
+++ b/keyserver/src/utils/validation-utils.test.js
@@ -24,4 +24,51 @@
const redacted = { password: redactedString };
expect(sanitizeInput(validator, object)).toStrictEqual(redacted);
});
+
+ it('should redact a string in optional object', () => {
+ const validator = tShape({ obj: t.maybe(tShape({ password: tPassword })) });
+ const object = { obj: { password: 'password' } };
+ const redacted = { obj: { password: redactedString } };
+ expect(sanitizeInput(validator, object)).toStrictEqual(redacted);
+ });
+
+ it('should redact a string array', () => {
+ const validator = tShape({ passwords: t.list(tPassword) });
+ const object = { passwords: ['password', 'password'] };
+ const redacted = { passwords: [redactedString, redactedString] };
+ expect(sanitizeInput(validator, object)).toStrictEqual(redacted);
+ });
+
+ it('should redact a string inside a dict', () => {
+ const validator = tShape({ passwords: t.dict(t.String, tPassword) });
+ const object = { passwords: { a: 'password', b: 'password' } };
+ const redacted = { passwords: { a: redactedString, b: redactedString } };
+ expect(sanitizeInput(validator, object)).toStrictEqual(redacted);
+ });
+
+ it('should redact password dict key', () => {
+ const validator = tShape({ passwords: t.dict(tPassword, t.Bool) });
+ const object = { passwords: { password1: true, password2: false } };
+ const redacted = { passwords: {} };
+ redacted.passwords[redactedString] = false;
+ expect(sanitizeInput(validator, object)).toStrictEqual(redacted);
+ });
+
+ it('should redact a string inside a union', () => {
+ const validator = tShape({
+ password: t.union([tPassword, t.String, t.Bool]),
+ });
+ const object = { password: 'password' };
+ const redacted = { password: redactedString };
+ expect(sanitizeInput(validator, object)).toStrictEqual(redacted);
+ });
+
+ it('should redact a string inside an object array', () => {
+ const validator = tShape({
+ passwords: t.list(tShape({ password: tPassword })),
+ });
+ const object = { passwords: [{ password: 'password' }] };
+ const redacted = { passwords: [{ password: redactedString }] };
+ expect(sanitizeInput(validator, object)).toStrictEqual(redacted);
+ });
});

File Metadata

Mime Type
text/plain
Expires
Wed, Nov 27, 3:35 AM (20 h, 57 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2587576
Default Alt Text
D7488.id25273.diff (7 KB)

Event Timeline