Bug 1440782 Part 1 - Move PolicyEngine's JSON schema validator to toolkit r?felipe draft
authorMike Cooper <mcooper@mozilla.com>
Tue, 17 Apr 2018 16:36:05 -0700
changeset 785329 c8ae85f4be9bfcc04b7f1f2b01241d54e2d070d4
parent 783184 6a87ef9c1acc5f6af848c00b8b9f608444ff8a72
child 785330 4850c0ace4797ec4c5ae9a63ad8f02b032b4ae17
push id107199
push userbmo:mcooper@mozilla.com
push dateThu, 19 Apr 2018 22:38:16 +0000
reviewersfelipe
bugs1440782
milestone61.0a1
Bug 1440782 Part 1 - Move PolicyEngine's JSON schema validator to toolkit r?felipe MozReview-Commit-ID: 41K9xzry21w
browser/components/enterprisepolicies/EnterprisePolicies.js
browser/components/enterprisepolicies/PoliciesValidator.jsm
browser/components/enterprisepolicies/moz.build
browser/components/enterprisepolicies/tests/browser/browser.ini
browser/components/enterprisepolicies/tests/browser/browser_policies_validate_and_parse_API.js
toolkit/components/normandy/actions/BaseAction.jsm
toolkit/components/utils/JsonSchemaValidator.jsm
toolkit/components/utils/moz.build
toolkit/components/utils/test/browser/.eslintrc.js
toolkit/components/utils/test/browser/browser.ini
toolkit/components/utils/test/browser/browser_JsonSchemaValidator.js
--- a/browser/components/enterprisepolicies/EnterprisePolicies.js
+++ b/browser/components/enterprisepolicies/EnterprisePolicies.js
@@ -4,17 +4,17 @@
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   WindowsGPOParser: "resource:///modules/policies/WindowsGPOParser.jsm",
   Policies: "resource:///modules/policies/Policies.jsm",
-  PoliciesValidator: "resource:///modules/policies/PoliciesValidator.jsm",
+  JsonSchemaValidator: "resource://gre/modules/components-utils/JsonSchemaValidator.jsm",
 });
 
 // This is the file that will be searched for in the
 // ${InstallDir}/distribution folder.
 const POLICIES_FILENAME = "policies.json";
 
 // For easy testing, modify the helpers/sample.json file,
 // and set PREF_ALTERNATE_PATH in firefox.js as:
@@ -122,18 +122,17 @@ EnterprisePoliciesManager.prototype = {
       }
 
       if (policySchema.enterprise_only && !areEnterpriseOnlyPoliciesAllowed()) {
         log.error(`Policy ${policyName} is only allowed on ESR`);
         continue;
       }
 
       let [parametersAreValid, parsedParameters] =
-        PoliciesValidator.validateAndParseParameters(policyParameters,
-                                                     policySchema);
+        JsonSchemaValidator.validateAndParseParameters(policyParameters, policySchema);
 
       if (!parametersAreValid) {
         log.error(`Invalid parameters specified for ${policyName}.`);
         continue;
       }
 
       let policyImpl = Policies[policyName];
 
--- a/browser/components/enterprisepolicies/moz.build
+++ b/browser/components/enterprisepolicies/moz.build
@@ -19,17 +19,16 @@ TEST_DIRS += [
 EXTRA_COMPONENTS += [
     'EnterprisePolicies.js',
     'EnterprisePolicies.manifest',
     'EnterprisePoliciesContent.js',
 ]
 
 EXTRA_JS_MODULES.policies += [
     'Policies.jsm',
-    'PoliciesValidator.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXTRA_JS_MODULES.policies += [
         'WindowsGPOParser.jsm',
 ]
 
 FINAL_LIBRARY = 'browsercomps'
--- a/browser/components/enterprisepolicies/tests/browser/browser.ini
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -13,17 +13,16 @@ support-files =
 [browser_policies_broken_json.js]
 [browser_policies_enterprise_only.js]
 [browser_policies_notice_in_aboutpreferences.js]
 [browser_policies_popups_cookies_addons_flash.js]
 [browser_policies_runOnce_helper.js]
 [browser_policies_setAndLockPref_API.js]
 [browser_policies_simple_pref_policies.js]
 [browser_policies_sorted_alphabetically.js]
-[browser_policies_validate_and_parse_API.js]
 [browser_policy_app_update.js]
 [browser_policy_block_about_addons.js]
 [browser_policy_block_about_config.js]
 [browser_policy_block_about_profiles.js]
 [browser_policy_block_about_support.js]
 [browser_policy_block_set_desktop_background.js]
 [browser_policy_bookmarks.js]
 [browser_policy_clear_blocked_cookies.js]
--- a/toolkit/components/normandy/actions/BaseAction.jsm
+++ b/toolkit/components/normandy/actions/BaseAction.jsm
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 ChromeUtils.defineModuleGetter(this, "LogManager", "resource://normandy/lib/LogManager.jsm");
 ChromeUtils.defineModuleGetter(this, "Uptake", "resource://normandy/lib/Uptake.jsm");
-ChromeUtils.defineModuleGetter(this, "PoliciesValidator", "resource:///modules/policies/PoliciesValidator.jsm");
+ChromeUtils.defineModuleGetter(this, "JsonSchemaValidator", "resource://gre/modules/components-utils/JsonSchemaValidator.jsm");
 
 var EXPORTED_SYMBOLS = ["BaseAction"];
 
 /**
  * Base class for local actions.
  *
  * This should be subclassed. Subclasses must implement _run() for
  * per-recipe behavior, and may implement _preExecution and _finalize
@@ -67,17 +67,17 @@ class BaseAction {
     }
 
     if (this.failed) {
       Uptake.reportRecipe(recipe.id, Uptake.RECIPE_ACTION_DISABLED);
       this.log.warn(`Skipping recipe ${recipe.name} because ${this.name} failed during preExecution.`);
       return;
     }
 
-    let [valid, validatedArguments] = PoliciesValidator.validateAndParseParameters(recipe.arguments, this.schema);
+    let [valid, validatedArguments] = JsonSchemaValidator.validateAndParseParameters(recipe.arguments, this.schema);
     if (!valid) {
       Cu.reportError(new Error(`Arguments do not match schema. arguments: ${JSON.stringify(recipe.arguments)}. schema: ${JSON.stringify(this.schema)}`));
       Uptake.reportRecipe(recipe.id, Uptake.RECIPE_EXECUTION_ERROR);
       return;
     }
 
     recipe.arguments = validatedArguments;
 
rename from browser/components/enterprisepolicies/PoliciesValidator.jsm
rename to toolkit/components/utils/JsonSchemaValidator.jsm
--- a/browser/components/enterprisepolicies/PoliciesValidator.jsm
+++ b/toolkit/components/utils/JsonSchemaValidator.jsm
@@ -1,47 +1,72 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* This file implements a not-quite standard JSON schema validator. It differs
+ * from the spec in a few ways:
+ *
+ *  - the spec doesn't allow custom types to be defined, but this validator
+ *    defines "URL", "URLorEmpty", "origin" etc.
+ * - Strings are automatically converted to nsIURIs for the appropriate types.
+ * - It doesn't support "pattern" when matching strings.
+ * - The boolean type accepts (and casts) 0 and 1 as valid values.
+ */
+
 "use strict";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-const PREF_LOGLEVEL           = "browser.policies.loglevel";
-
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let { ConsoleAPI } = ChromeUtils.import("resource://gre/modules/Console.jsm", {});
   return new ConsoleAPI({
-    prefix: "PoliciesValidator.jsm",
+    prefix: "JsonSchemaValidator.jsm",
     // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
     // messages during development. See LOG_LEVELS in Console.jsm for details.
     maxLogLevel: "error",
-    maxLogLevelPref: PREF_LOGLEVEL,
   });
 });
 
-var EXPORTED_SYMBOLS = ["PoliciesValidator"];
+var EXPORTED_SYMBOLS = ["JsonSchemaValidator"];
 
-var PoliciesValidator = {
+var JsonSchemaValidator = {
   validateAndParseParameters(param, properties) {
     return validateAndParseParamRecursive(param, properties);
   }
 };
 
 function validateAndParseParamRecursive(param, properties) {
   if (properties.enum) {
     if (properties.enum.includes(param)) {
       return [true, param];
     }
     return [false, null];
   }
 
   log.debug(`checking @${param}@ for type ${properties.type}`);
+
+  if (Array.isArray(properties.type)) {
+    log.debug("type is an array");
+    // For an array of types, the value is valid if it matches any of the listed
+    // types. To check this, make versions of the object definition that include
+    // only one type at a time, and check the value against each one.
+    for (const type of properties.type) {
+      let typeProperties = Object.assign({}, properties, {type});
+      log.debug(`checking subtype ${type}`);
+      let [valid, data] = validateAndParseParamRecursive(param, typeProperties);
+      if (valid) {
+        return [true, data];
+      }
+    }
+    // None of the types matched
+    return [false, null];
+  }
+
   switch (properties.type) {
     case "boolean":
     case "number":
     case "integer":
     case "string":
     case "URL":
     case "URLorEmpty":
     case "origin":
--- a/toolkit/components/utils/moz.build
+++ b/toolkit/components/utils/moz.build
@@ -8,10 +8,13 @@ with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'General')
 
 EXTRA_COMPONENTS += [
     'simpleServices.js',
     'utils.manifest',
 ]
 
 EXTRA_JS_MODULES['components-utils'] = [
-    'ClientEnvironment.jsm'
+    'ClientEnvironment.jsm',
+    'JsonSchemaValidator.jsm',
 ]
+
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
copy from toolkit/components/normandy/test/browser/.eslintrc.js
copy to toolkit/components/utils/test/browser/.eslintrc.js
new file mode 100644
--- /dev/null
+++ b/toolkit/components/utils/test/browser/browser.ini
@@ -0,0 +1,1 @@
+[browser_JsonSchemaValidator.js]
rename from browser/components/enterprisepolicies/tests/browser/browser_policies_validate_and_parse_API.js
rename to toolkit/components/utils/test/browser/browser_JsonSchemaValidator.js
--- a/browser/components/enterprisepolicies/tests/browser/browser_policies_validate_and_parse_API.js
+++ b/toolkit/components/utils/test/browser/browser_JsonSchemaValidator.js
@@ -1,226 +1,222 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-/* This file will test the parameters parsing and validation directly through
-   the PoliciesValidator API.
- */
-
-const { PoliciesValidator } = ChromeUtils.import("resource:///modules/policies/PoliciesValidator.jsm", {});
+ChromeUtils.import("resource://gre/modules/components-utils/JsonSchemaValidator.jsm", this);
 
 add_task(async function test_boolean_values() {
   let schema = {
     type: "boolean"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(true, schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(true, schema);
   ok(valid && parsed === true, "Parsed boolean value correctly");
 
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(false, schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(false, schema);
   ok(valid && parsed === false, "Parsed boolean value correctly");
 
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(0, schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(0, schema);
   ok(valid && parsed === false, "0 parsed as false correctly");
 
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(1, schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(1, schema);
   ok(valid && parsed === true, "1 parsed as true correctly");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters("0", schema)[0], "No type coercion");
-  ok(!PoliciesValidator.validateAndParseParameters("true", schema)[0], "No type coercion");
-  ok(!PoliciesValidator.validateAndParseParameters(2, schema)[0], "Other number values are not valid");
-  ok(!PoliciesValidator.validateAndParseParameters(undefined, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters("0", schema)[0], "No type coercion");
+  ok(!JsonSchemaValidator.validateAndParseParameters("true", schema)[0], "No type coercion");
+  ok(!JsonSchemaValidator.validateAndParseParameters(2, schema)[0], "Other number values are not valid");
+  ok(!JsonSchemaValidator.validateAndParseParameters(undefined, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
 });
 
 add_task(async function test_number_values() {
   let schema = {
     type: "number"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(1, schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(1, schema);
   ok(valid && parsed === 1, "Parsed number value correctly");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters("1", schema)[0], "No type coercion");
-  ok(!PoliciesValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters("1", schema)[0], "No type coercion");
+  ok(!JsonSchemaValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
 });
 
 add_task(async function test_integer_values() {
   // Integer is an alias for number
   let schema = {
     type: "integer"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(1, schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(1, schema);
   ok(valid && parsed == 1, "Parsed integer value correctly");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters("1", schema)[0], "No type coercion");
-  ok(!PoliciesValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters("1", schema)[0], "No type coercion");
+  ok(!JsonSchemaValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
 });
 
 add_task(async function test_string_values() {
   let schema = {
     type: "string"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters("foobar", schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("foobar", schema);
   ok(valid && parsed == "foobar", "Parsed string value correctly");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters(1, schema)[0], "No type coercion");
-  ok(!PoliciesValidator.validateAndParseParameters(true, schema)[0], "No type coercion");
-  ok(!PoliciesValidator.validateAndParseParameters(undefined, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
-  ok(!PoliciesValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(1, schema)[0], "No type coercion");
+  ok(!JsonSchemaValidator.validateAndParseParameters(true, schema)[0], "No type coercion");
+  ok(!JsonSchemaValidator.validateAndParseParameters(undefined, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
 });
 
 add_task(async function test_URL_values() {
   let schema = {
     type: "URL"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters("https://www.example.com/foo#bar", schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("https://www.example.com/foo#bar", schema);
   ok(valid, "URL is valid");
   ok(parsed instanceof Ci.nsIURI, "parsed is a nsIURI");
   is(parsed.prePath, "https://www.example.com", "prePath is correct");
   is(parsed.pathQueryRef, "/foo#bar", "pathQueryRef is correct");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters("", schema)[0], "Empty string is not accepted for URL");
-  ok(!PoliciesValidator.validateAndParseParameters("www.example.com", schema)[0], "Scheme is required for URL");
-  ok(!PoliciesValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid URL");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters("", schema)[0], "Empty string is not accepted for URL");
+  ok(!JsonSchemaValidator.validateAndParseParameters("www.example.com", schema)[0], "Scheme is required for URL");
+  ok(!JsonSchemaValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid URL");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
 });
 
 add_task(async function test_URLorEmpty_values() {
   let schema = {
     type: "URLorEmpty"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters("https://www.example.com/foo#bar", schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("https://www.example.com/foo#bar", schema);
   ok(valid, "URL is valid");
   ok(parsed instanceof Ci.nsIURI, "parsed is a nsIURI");
   is(parsed.prePath, "https://www.example.com", "prePath is correct");
   is(parsed.pathQueryRef, "/foo#bar", "pathQueryRef is correct");
 
   // Test that this type also accept empty strings
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters("", schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("", schema);
   ok(valid, "URLorEmpty is valid");
   ok(!parsed, "parsed value is falsy");
   is(typeof(parsed), "string", "parsed is a string");
   is(parsed, "", "parsed is an empty string");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters(" ", schema)[0], "Non-empty string is not accepted");
-  ok(!PoliciesValidator.validateAndParseParameters("www.example.com", schema)[0], "Scheme is required for URL");
-  ok(!PoliciesValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid URL");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(" ", schema)[0], "Non-empty string is not accepted");
+  ok(!JsonSchemaValidator.validateAndParseParameters("www.example.com", schema)[0], "Scheme is required for URL");
+  ok(!JsonSchemaValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid URL");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
 });
 
 
 add_task(async function test_origin_values() {
   // Origin is a URL that doesn't contain a path/query string (i.e., it's only scheme + host + port)
   let schema = {
     type: "origin"
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters("https://www.example.com", schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("https://www.example.com", schema);
   ok(valid, "Origin is valid");
   ok(parsed instanceof Ci.nsIURI, "parsed is a nsIURI");
   is(parsed.prePath, "https://www.example.com", "prePath is correct");
   is(parsed.pathQueryRef, "/", "pathQueryRef is corect");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters("https://www.example.com/foobar", schema)[0], "Origin cannot contain a path part");
-  ok(!PoliciesValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid origin");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters("https://www.example.com/foobar", schema)[0], "Origin cannot contain a path part");
+  ok(!JsonSchemaValidator.validateAndParseParameters("https://:!$%", schema)[0], "Invalid origin");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
 });
 
 add_task(async function test_array_values() {
   // The types inside an array object must all be the same
   let schema = {
     type: "array",
     items: {
       type: "number"
     }
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters([1, 2, 3], schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters([1, 2, 3], schema);
   ok(valid, "Array is valid");
   ok(Array.isArray(parsed), "parsed is an array");
   is(parsed.length, 3, "array is correct");
 
   // An empty array is also valid
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters([], schema);
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters([], schema);
   ok(valid, "Array is valid");
   ok(Array.isArray(parsed), "parsed is an array");
   is(parsed.length, 0, "array is correct");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters([1, true, 3], schema)[0], "Mixed types");
-  ok(!PoliciesValidator.validateAndParseParameters(2, schema)[0], "Type is correct but not in an array");
-  ok(!PoliciesValidator.validateAndParseParameters({}, schema)[0], "Object is not an array");
+  ok(!JsonSchemaValidator.validateAndParseParameters([1, true, 3], schema)[0], "Mixed types");
+  ok(!JsonSchemaValidator.validateAndParseParameters(2, schema)[0], "Type is correct but not in an array");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Object is not an array");
 });
 
 add_task(async function test_object_values() {
   let schema = {
     type: "object",
     properties: {
       url: {
         type: "URL"
       },
       title: {
         type: "string"
       }
     }
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(
     {
       url: "https://www.example.com/foo#bar",
       title: "Foo",
       alias: "Bar"
     },
     schema);
 
   ok(valid, "Object is valid");
   ok(typeof(parsed) == "object", "parsed in an object");
   ok(parsed.url instanceof Ci.nsIURI, "types inside the object are also parsed");
   is(parsed.url.spec, "https://www.example.com/foo#bar", "URL was correctly parsed");
   is(parsed.title, "Foo", "title was correctly parsed");
   is(parsed.alias, undefined, "property not described in the schema is not present in the parsed object");
 
   // Invalid values:
-  ok(!PoliciesValidator.validateAndParseParameters(
+  ok(!JsonSchemaValidator.validateAndParseParameters(
     {
       url: "https://www.example.com/foo#bar",
       title: 3,
     },
     schema)[0], "Mismatched type for title");
 
-  ok(!PoliciesValidator.validateAndParseParameters(
+  ok(!JsonSchemaValidator.validateAndParseParameters(
     {
       url: "www.example.com",
       title: 3,
     },
     schema)[0], "Invalid URL inside the object");
 });
 
 add_task(async function test_array_of_objects() {
@@ -236,17 +232,17 @@ add_task(async function test_array_of_ob
         title: {
           type: "string"
         }
       }
     }
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters(
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(
     [{
       url: "https://www.example.com/bookmark1",
       title: "Foo",
     },
     {
       url: "https://www.example.com/bookmark2",
       title: "Bar",
     }],
@@ -280,17 +276,17 @@ add_task(async function test_missing_arr
           type: "boolean"
         }
       }
 
     }
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters({
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters({
     allow: [true, true, true]
   }, schema);
 
   ok(valid, "Object is valid");
   is(parsed.allow.length, 3, "Allow array is correct.");
   is(parsed.block, undefined, "Block array is undefined, as expected.");
 });
 
@@ -305,23 +301,71 @@ add_task(async function test_required_vs
       "required-property": {
         type: "number"
       }
     },
     required: ["required-property"]
   };
 
   let valid, parsed;
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters({
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters({
     "required-property": 5
   }, schema);
 
   ok(valid, "Object is valid since required property is present");
   is(parsed["required-property"], 5, "required property is correct");
   is(parsed["non-required-property"], undefined, "non-required property is undefined, as expected");
 
-  [valid, parsed] = PoliciesValidator.validateAndParseParameters({
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters({
     "non-required-property": 5
   }, schema);
 
   ok(!valid, "Object is not valid since the required property is missing");
   is(parsed, null, "Nothing was returned as parsed");
 });
+
+add_task(async function test_number_or_string_values() {
+  let schema = {
+    type: ["number", "string"],
+  };
+
+  let valid, parsed;
+  // valid values
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(1, schema);
+  ok(valid && parsed === 1, "Parsed number value correctly");
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("foobar", schema);
+  ok(valid && parsed === "foobar", "Parsed string value correctly");
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters("1", schema);
+  ok(valid && parsed === "1", "Did not coerce string to number");
+
+  // Invalid values:
+  ok(!JsonSchemaValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+});
+
+add_task(async function test_number_or_array_values() {
+  let schema = {
+    type: ["number", "array"],
+    items: {
+      type: "number",
+    }
+  };
+
+  let valid, parsed;
+  // valid values
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters(1, schema);
+  ok(valid, "Number is valid");
+  is(parsed, 1, "Parsed correctly");
+  ok(valid && parsed === 1, "Parsed number value correctly");
+
+  [valid, parsed] = JsonSchemaValidator.validateAndParseParameters([1, 2, 3], schema);
+  ok(valid, "Array is valid");
+  Assert.deepEqual(parsed, [1, 2, 3], "Parsed correctly");
+
+  // Invalid values:
+  ok(!JsonSchemaValidator.validateAndParseParameters(true, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters({}, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(null, schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters(["a", "b"], schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters([[]], schema)[0], "Invalid value");
+  ok(!JsonSchemaValidator.validateAndParseParameters([0, 1, [2, 3]], schema)[0], "Invalid value");
+});