confidential_helpers.ts 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. /**
  2. * Copyright © 2016-2019 Threema GmbH (https://threema.ch/).
  3. *
  4. * This file is part of Threema Web.
  5. *
  6. * Threema Web is free software: you can redistribute it and/or modify it
  7. * under the terms of the GNU Affero General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or (at
  9. * your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but
  12. * WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. import {
  20. censor,
  21. BaseConfidential,
  22. ConfidentialArray,
  23. ConfidentialObjectValues,
  24. ConfidentialWireMessage
  25. } from '../../src/helpers/confidential';
  26. // tslint:disable:no-reference
  27. /// <reference path="../../src/threema.d.ts" />
  28. class UnlistedClass {}
  29. /**
  30. * A confidential subclass for testing purposes.
  31. */
  32. class TestConfidential extends BaseConfidential<string, string> {
  33. public readonly uncensored: string = 'uncensored';
  34. public censored(): string {
  35. return 'censored';
  36. }
  37. }
  38. describe('Confidential Helpers', () => {
  39. describe('censor function', () => {
  40. it('handles null and undefined', () => {
  41. expect(censor(null)).toBe(null);
  42. expect(censor(undefined)).toBe(undefined);
  43. });
  44. it('handles an object implementing the Confidential interface', () => {
  45. expect(censor(new TestConfidential())).toBe('censored');
  46. });
  47. it('handles booleans', () => {
  48. expect(censor(true)).toBe('[Boolean]');
  49. expect(censor(false)).toBe('[Boolean]');
  50. });
  51. it('handles numbers', () => {
  52. expect(censor(0)).toBe('[Number]');
  53. expect(censor(42)).toBe('[Number]');
  54. expect(censor(-1337)).toBe('[Number]');
  55. });
  56. it('handles strings', () => {
  57. expect(censor('test')).toBe('[String: length=4]');
  58. expect(censor('')).toBe('[String: length=0]');
  59. });
  60. it('handles binary types', () => {
  61. const buffer = new ArrayBuffer(10);
  62. const array = new Uint8Array(buffer, 2, 6);
  63. const blob = new Blob([JSON.stringify({ a: 10 })], { type: 'application/json'} );
  64. expect(censor(buffer)).toBe('[ArrayBuffer: length=10]');
  65. expect(censor(array)).toBe('[Uint8Array: length=6, offset=2]');
  66. expect(censor(blob)).toBe(`[Blob: length=${blob.size}, type=application/json]`);
  67. });
  68. it('handles arrays', () => {
  69. expect(censor([
  70. null,
  71. undefined,
  72. new TestConfidential(),
  73. false,
  74. 42,
  75. 'test',
  76. new Uint8Array(10),
  77. ])).toEqual([
  78. null,
  79. undefined,
  80. 'censored',
  81. '[Boolean]',
  82. '[Number]',
  83. '[String: length=4]',
  84. '[Uint8Array: length=10, offset=0]',
  85. ]);
  86. });
  87. it('handles arrays recursively', () => {
  88. expect(censor([
  89. 'test',
  90. [1, false],
  91. ])).toEqual([
  92. '[String: length=4]',
  93. ['[Number]', '[Boolean]'],
  94. ]);
  95. });
  96. it('handles objects', () => {
  97. expect(censor({
  98. null: null,
  99. undefined: undefined,
  100. confidential: new TestConfidential(),
  101. boolean: false,
  102. number: 42,
  103. string: 'test',
  104. uint8array: new Uint8Array(10),
  105. })).toEqual({
  106. null: null,
  107. undefined: undefined,
  108. confidential: 'censored',
  109. boolean: '[Boolean]',
  110. number: '[Number]',
  111. string: '[String: length=4]',
  112. uint8array: '[Uint8Array: length=10, offset=0]',
  113. });
  114. });
  115. it('handles objects recursively', () => {
  116. expect(censor({
  117. boolean: false,
  118. object: {
  119. foo: 'bar',
  120. },
  121. })).toEqual({
  122. boolean: '[Boolean]',
  123. object: {
  124. foo: '[String: length=3]',
  125. },
  126. });
  127. });
  128. it('handles class instances', () => {
  129. expect(censor(new UnlistedClass())).toBe('[UnlistedClass]');
  130. });
  131. });
  132. describe('ConfidentialArray', () => {
  133. it('subclass of BaseConfidential', () => {
  134. expect(ConfidentialArray.prototype instanceof BaseConfidential).toBeTruthy();
  135. });
  136. it('sanitises all items', () => {
  137. const array = new ConfidentialArray([new TestConfidential(), new TestConfidential()]);
  138. expect(array.uncensored).toEqual(['uncensored', 'uncensored']);
  139. expect(array.censored()).toEqual(['censored', 'censored']);
  140. });
  141. it('sanitises all items recursively', () => {
  142. const array = new ConfidentialArray([
  143. new TestConfidential(),
  144. new ConfidentialArray([new TestConfidential()]),
  145. ]);
  146. expect(array.uncensored).toEqual(['uncensored', ['uncensored']]);
  147. expect(array.censored()).toEqual(['censored', ['censored']]);
  148. });
  149. });
  150. describe('ConfidentialObjectValues', () => {
  151. it('subclass of BaseConfidential', () => {
  152. expect(ConfidentialObjectValues.prototype instanceof BaseConfidential).toBeTruthy();
  153. });
  154. it('returns underlying object directly when unveiling', () => {
  155. const object = {};
  156. const confidential = new ConfidentialObjectValues(object);
  157. expect(confidential.uncensored).toBe(object);
  158. });
  159. it('sanitises all object values', () => {
  160. const object = {
  161. boolean: false,
  162. object: {
  163. foo: 'bar',
  164. },
  165. };
  166. const confidential = new ConfidentialObjectValues(object);
  167. expect(confidential.uncensored).toBe(object);
  168. expect(confidential.censored()).toEqual({
  169. boolean: '[Boolean]',
  170. object: {
  171. foo: '[String: length=3]',
  172. },
  173. });
  174. });
  175. });
  176. describe('ConfidentialWireMessage', () => {
  177. it('subclass of BaseConfidential', () => {
  178. expect(ConfidentialWireMessage.prototype instanceof BaseConfidential).toBeTruthy();
  179. });
  180. it('returns underlying message directly when unveiling', () => {
  181. const message = {
  182. type: 'request/food',
  183. subType: 'dessert',
  184. };
  185. const confidential = new ConfidentialWireMessage(message);
  186. expect(confidential.uncensored).toBe(message);
  187. });
  188. it("handles 'args' and 'data' being undefined", () => {
  189. const message = {
  190. type: 'request/food',
  191. subType: 'dessert',
  192. };
  193. const confidential = new ConfidentialWireMessage(message);
  194. expect(confidential.censored()).toEqual(message);
  195. });
  196. it("sanitises 'args' and 'data' fields", () => {
  197. const message = {
  198. type: 'request/food',
  199. subType: 'dessert',
  200. args: 'arrrrrrgggsss',
  201. data: {
  202. preference: ['ice cream', 'chocolate'],
  203. priority: Number.POSITIVE_INFINITY,
  204. },
  205. };
  206. const confidential = new ConfidentialWireMessage(message);
  207. expect(confidential.censored()).toEqual({
  208. type: 'request/food',
  209. subType: 'dessert',
  210. args: '[String: length=13]',
  211. data: {
  212. preference: ['[String: length=9]', '[String: length=9]'],
  213. priority: '[Number]',
  214. },
  215. });
  216. });
  217. });
  218. });