diff --git a/RWS-Submission_Guidelines.md b/RWS-Submission_Guidelines.md
index faf210b8..014c2169 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.
@@ -141,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:
- The /.well-known/ metadata requirement demonstrates that the submitter has administrative access to the domains present in the set, since administrative access is required to modify the /.well-known/ file. This will help prevent unauthorized actors from adding domains to a set.
- - The primary domain must serve a JSON file at /.well-known/related-website-set.json (Note: list entries merged before September 15th 2023 may serve their well-known file at /.well-known/related-website-set.json instead; however, any changes to those entries will require that the primary and all members of the set must be served at /.well-known/related-website-set.json like any other entry). The contents of the file must be identical to the submission. Each member domain must serve a JSON file at /.well-known/related-website-set.json. The contents of the file must name the primary domain. These files must be maintained for the duration of the domain’s inclusion in the set.
+ - The primary domain must serve a JSON file at /.well-known/related-website-set.json (Note: list entries merged before September 15th 2023 may serve their well-known file at /.well-known/first-party-set.json instead; however, any changes to those entries will require that the primary and all members of the set must be served at /.well-known/related-website-set.json like any other entry). The contents of the file must be identical to the submission. Each member domain must serve a JSON file at /.well-known/related-website-set.json. The contents of the file must name the primary domain. These files must be maintained for the duration of the domain’s inclusion in the set.
- Any changes to an existing RWS in the canonical RWS list must also be reflected in that set's JSON files at /.well-known/related-website-set.json.
- If an RWS owner wishes to remove a set entirely from the canonical RWS list, then that set's primary must serve a `404 (Not Found)` status code at their /.well-known/related-website-set.json endpoint to demonstrate a deliberate desire to remove the set.
- Example for primary.com/.well-known/related-website-set.json:
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):