Page MenuHomePhabricator

D7488.id26105.diff
No OneTemporary

D7488.id26105.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,9 @@
// @flow
+import _mapKeys from 'lodash/fp/mapKeys.js';
+import _mapValues from 'lodash/fp/mapValues.js';
+import type { TType, TInterface } 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';
@@ -8,20 +12,25 @@
tPassword,
tPlatform,
tPlatformDetails,
+ assertWithValidator,
} 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: *) {
+async function validateInput<T>(
+ viewer: Viewer,
+ inputValidator: ?TType<T>,
+ input: T,
+) {
if (!viewer.isSocket) {
await checkClientSupported(viewer, inputValidator, input);
}
checkInputValidator(inputValidator, input);
}
-function checkInputValidator(inputValidator: *, input: *) {
+function checkInputValidator<T>(inputValidator: ?TType<T>, input: T) {
if (!inputValidator || inputValidator.is(input)) {
return;
}
@@ -30,10 +39,10 @@
throw error;
}
-async function checkClientSupported(
+async function checkClientSupported<T>(
viewer: Viewer,
- inputValidator: *,
- input: *,
+ inputValidator: ?TType<T>,
+ input: T,
) {
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,87 @@
return null;
}
+function convertObject<T, I>(
+ validator: TType<I>,
+ input: I,
+ typesToConvert: $ReadOnlyArray<TType<T>>,
+ conversionFunction: T => T,
+): I {
+ if (input === null || input === undefined) {
+ return input;
+ }
+
+ // While they should be the same runtime object,
+ // `TValidator` is `TType<T>` and `validator` is `TType<I>`.
+ // Having them have different types allows us to use `assertWithValidator`
+ // to change `input` flow type
+ const TValidator = typesToConvert[typesToConvert.indexOf(validator)];
+ if (TValidator && TValidator.is(input)) {
+ const TInput = assertWithValidator(input, TValidator);
+ const converted = conversionFunction(TInput);
+ return assertWithValidator(converted, validator);
+ }
+
+ if (validator.meta.kind === 'maybe') {
+ return convertObject(
+ validator.meta.type,
+ input,
+ typesToConvert,
+ conversionFunction,
+ );
+ }
+ if (validator.meta.kind === 'interface' && typeof input === 'object') {
+ const recastValidator: TInterface<typeof input> = (validator: any);
+ const result = {};
+ for (const key in input) {
+ const innerValidator = recastValidator.meta.props[key];
+ result[key] = convertObject(
+ innerValidator,
+ input[key],
+ typesToConvert,
+ conversionFunction,
+ );
+ }
+ return assertWithValidator(result, recastValidator);
+ }
+ 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 (input.map(value =>
+ convertObject(innerValidator, value, typesToConvert, conversionFunction),
+ ): any);
+ }
+ 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 +255,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);
+ });
});
diff --git a/lib/utils/validation-utils.js b/lib/utils/validation-utils.js
--- a/lib/utils/validation-utils.js
+++ b/lib/utils/validation-utils.js
@@ -1,5 +1,6 @@
// @flow
+import invariant from 'invariant';
import t from 'tcomb';
import type {
TStructProps,
@@ -8,6 +9,7 @@
TEnums,
TInterface,
TUnion,
+ TType,
} from 'tcomb';
import {
@@ -94,6 +96,11 @@
tMediaMessageVideo,
]);
+function assertWithValidator<T>(data: mixed, validator: TType<T>): T {
+ invariant(validator.is(data), "data isn't of type T");
+ return (data: any);
+}
+
export {
tBool,
tString,
@@ -114,4 +121,5 @@
tMediaMessagePhoto,
tMediaMessageVideo,
tMediaMessageMedia,
+ assertWithValidator,
};

File Metadata

Mime Type
text/plain
Expires
Wed, Nov 27, 2:30 AM (20 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2587383
Default Alt Text
D7488.id26105.diff (8 KB)

Event Timeline