From 8296ccf8dc89045d3f1a28affd6663b351e1a74a Mon Sep 17 00:00:00 2001 From: Chris Fredrickson Date: Fri, 13 Dec 2024 12:20:02 -0500 Subject: [PATCH 1/2] Fix schema, make it stricter, add tests (#705) * Fix schema, make it stricter, add tests * format --- RWS-Submission_Guidelines.md | 52 +----------- SCHEMA.json | 16 +++- tests/rws_tests.py | 159 +++++++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 54 deletions(-) diff --git a/RWS-Submission_Guidelines.md b/RWS-Submission_Guidelines.md index faf210b8..a9ef8ab1 100644 --- a/RWS-Submission_Guidelines.md +++ b/RWS-Submission_Guidelines.md @@ -44,57 +44,7 @@ ccTLD (country code top-level domain) variants for the subsets above are also su New submissions to the canonical RWS list must be filed as pull requests (PRs) on GitHub. Submitters should ensure that submissions follow the schema template provided below. Anyone with a GitHub account may make a submission. Modifications to existing sets, including deletions, must also be submitted as new PRs against the canonical RWS list. -The canonical RWS list will be validated against this schema whenever a user files their PR: - -```json -{ - "type": "object", - "properties": { - "sets": { - "type": "array", - "items": { - "type": "object", - "properties": { - "contact" : {"type": "string"}, - "ccTLDs": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "primary": { "type": "string" }, - "associatedSites": { - "type": "array", - "items": { - "type": "string" - } - }, - "serviceSites": { - "type": "array", - "items": { - "type": "string" - } - }, - "rationaleBySite": { - "type": "object", - "additionalProperties": { - "type": "string" - } - } - }, - "required": ["primary"], - "dependentRequired": { - "associatedSites": ["An explanation of how you clearly present the affiliation across domains to users and why users would expect your domains to be affiliated"], - "serviceSites": ["An explanation of how each domain in this subset supports functionality or security needs."] - } - } - } - } -} -``` +The canonical RWS list will be validated against [this schema](./SCHEMA.json) whenever a user files their PR. A hypothetical example of the RWS canonical list is provided below for reference. A submission should follow the structure below, with new submissions being added as items to the "sets" list. diff --git a/SCHEMA.json b/SCHEMA.json index 388ae13b..fc54d62c 100644 --- a/SCHEMA.json +++ b/SCHEMA.json @@ -1,11 +1,13 @@ { "type": "object", + "additionalProperties": false, "properties": { - "contact" : {"type": "string"}, "sets": { "type": "array", + "uniqueItems": true, "items": { "type": "object", + "additionalProperties": false, "properties": { "ccTLDs": { "type": "object", @@ -16,15 +18,18 @@ } } }, + "contact": { "type": "string" }, "primary": {"type": "string"}, "associatedSites": { "type": "array", + "uniqueItems": true, "items": { "type": "string" } }, "serviceSites": { "type": "array", + "uniqueItems": true, "items": { "type": "string" } @@ -40,8 +45,13 @@ "dependentRequired": { "associatedSites": ["rationaleBySite"], "serviceSites": ["rationaleBySite"] - } + }, + "anyOf": [ + { "required": ["associatedSites"] }, + { "required": ["serviceSites"] }, + { "required": ["ccTLDs"] } + ] } } } -} \ No newline at end of file +} diff --git a/tests/rws_tests.py b/tests/rws_tests.py index 429d39ca..5c6c7d90 100644 --- a/tests/rws_tests.py +++ b/tests/rws_tests.py @@ -82,6 +82,87 @@ def test_parse_and_check_format(self): class TestValidateSchema(unittest.TestCase): """A test suite for the validate_schema function of RwsCheck""" + def test_valid_associated(self): + json_dict = { + "sets": [ + { + "contact": "abc@example.com", + "primary": "https://primary.com", + "associatedSites": ["https://associated1.com"], + "rationaleBySite": { + "https://associated1.com": "example rationale", + }, + } + ] + } + rws_check = RwsCheck(rws_sites=json_dict, etlds=None, icanns=set(["ca"])) + rws_check.validate_schema("SCHEMA.json") + + def test_valid_service(self): + json_dict = { + "sets": [ + { + "contact": "abc@example.com", + "primary": "https://primary.com", + "serviceSites": ["https://service1.com"], + "rationaleBySite": { + "https://service1.com": "example rationale", + }, + } + ] + } + rws_check = RwsCheck(rws_sites=json_dict, etlds=None, icanns=set(["ca"])) + rws_check.validate_schema("SCHEMA.json") + + def test_valid_ccTLDs(self): + json_dict = { + "sets": [ + { + "contact": "abc@example.com", + "primary": "https://primary.com", + "ccTLDs": {"https://primary.com": ["https://primary.ca"]}, + } + ] + } + rws_check = RwsCheck(rws_sites=json_dict, etlds=None, icanns=set(["ca"])) + rws_check.validate_schema("SCHEMA.json") + + def test_valid_full(self): + json_dict = { + "sets": [ + { + "contact": "abc@example.com", + "primary": "https://primary.com", + "associatedSites": ["https://associated1.com"], + "serviceSites": ["https://service1.com"], + "rationaleBySite": { + "https://associated1.com": "example rationale", + "https://service1.com": "example rationale", + }, + "ccTLDs": {"https://associated1.com": ["https://associated1.ca"]}, + } + ] + } + rws_check = RwsCheck(rws_sites=json_dict, etlds=None, icanns=set(["ca"])) + rws_check.validate_schema("SCHEMA.json") + + def test_duplicate_set(self): + entry = { + "contact": "abc@example.com", + "primary": "https://primary.com", + "associatedSites": ["https://associated1.com"], + "serviceSites": ["https://service1.com"], + "rationaleBySite": { + "https://associated1.com": "example rationale", + "https://service1.com": "example rationale", + }, + "ccTLDs": {"https://associated1.com": ["https://associated1.ca"]}, + } + json_dict = {"sets": [entry, entry]} + rws_check = RwsCheck(rws_sites=json_dict, etlds=None, icanns=set(["ca"])) + with self.assertRaises(ValidationError): + rws_check.validate_schema("SCHEMA.json") + def test_no_primary(self): json_dict = { "sets": [ @@ -101,6 +182,19 @@ def test_no_primary(self): with self.assertRaises(ValidationError): rws_check.validate_schema("SCHEMA.json") + def test_primary_only(self): + json_dict = { + "sets": [ + { + "contact": "abc@example.com", + "primary": "https://primary.com", + } + ] + } + rws_check = RwsCheck(rws_sites=json_dict, etlds=None, icanns=set(["ca"])) + with self.assertRaises(ValidationError): + rws_check.validate_schema("SCHEMA.json") + def test_no_rationaleBySite(self): json_dict = { "sets": [ @@ -150,6 +244,71 @@ def test_no_contact(self): with self.assertRaises(ValidationError): rws_check.validate_schema("SCHEMA.json") + def test_nonunique_associated_sites(self): + json_dict = { + "sets": [ + { + "contact": "abc@example.com", + "primary": "https://primary.com", + "associatedSites": [ + "https://associated1.com", + "https://associated1.com", + ], + "rationaleBySite": { + "https://associated1.com": "example rationale", + }, + } + ] + } + rws_check = RwsCheck(rws_sites=json_dict, etlds=None, icanns=set(["ca"])) + with self.assertRaises(ValidationError): + rws_check.validate_schema("SCHEMA.json") + + def test_nonunique_service_sites(self): + json_dict = { + "sets": [ + { + "contact": "abc@example.com", + "primary": "https://primary.com", + "serviceSites": [ + "https://service1.com", + "https://service1.com", + ], + "rationaleBySite": { + "https://service1.com": "example rationale", + }, + } + ] + } + rws_check = RwsCheck(rws_sites=json_dict, etlds=None, icanns=set(["ca"])) + with self.assertRaises(ValidationError): + rws_check.validate_schema("SCHEMA.json") + + def test_unexpected_top_level_property(self): + json_dict = {"sets": [], "foo": True} + rws_check = RwsCheck(rws_sites=json_dict, etlds=None, icanns=set(["ca"])) + with self.assertRaises(ValidationError): + rws_check.validate_schema("SCHEMA.json") + + def test_unexpected_set_level_property(self): + json_dict = { + "sets": [ + { + "contact": "abc@example.com", + "primary": "https://primary.com", + "associatedSites": ["https://associated1.com"], + "rationaleBySite": { + "https://associated1.com": "example rationale", + }, + "ccTLDs": {"https://associated1.com": ["https://associated1.ca"]}, + "foo": True, + } + ] + } + rws_check = RwsCheck(rws_sites=json_dict, etlds=None, icanns=set(["ca"])) + with self.assertRaises(ValidationError): + rws_check.validate_schema("SCHEMA.json") + class TestRwsSetEqual(unittest.TestCase): def test_equal_case(self): From 2391bef04e20ef38c88fc1cf47f6535139c8831d Mon Sep 17 00:00:00 2001 From: Chris Fredrickson Date: Fri, 13 Dec 2024 14:19:25 -0500 Subject: [PATCH 2/2] Fix erroneously-updated file path (#706) --- RWS-Submission_Guidelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RWS-Submission_Guidelines.md b/RWS-Submission_Guidelines.md index a9ef8ab1..014c2169 100644 --- a/RWS-Submission_Guidelines.md +++ b/RWS-Submission_Guidelines.md @@ -91,7 +91,7 @@ Upon submission of a PR, a series of technical checks will run on GitHub to veri
  • Each domain must satisfy the /.well-known/ metadata requirement: