diff --git a/filter_plugins/csp_filters.py b/filter_plugins/csp_filters.py index 005bfa73..b260c74b 100644 --- a/filter_plugins/csp_filters.py +++ b/filter_plugins/csp_filters.py @@ -127,7 +127,7 @@ class FilterModule(object): self.is_feature_enabled(applications, 'portfolio_iframe', application_id) and directive == 'frame-ancestors' ): - domain = domains.get('portfolio')[0] + domain = domains.get('web-app-port-ui')[0] sld_tld = ".".join(domain.split(".")[-2:]) # yields "example.com" tokens.append(f"{sld_tld}") # yields "*.example.com" diff --git a/tests/integration/test_domain_application_ids.py b/tests/integration/test_domain_application_ids.py new file mode 100644 index 00000000..ea82cbe3 --- /dev/null +++ b/tests/integration/test_domain_application_ids.py @@ -0,0 +1,82 @@ +import glob +import os +import re +import unittest + +from filter_plugins.get_all_application_ids import get_all_application_ids + + +def collect_domain_keys(base_dir="."): + """ + Scan all YAML and Python files under the project for usages of domains.get('...') and domains['...'] + and return a dict mapping each domain key to a list of file:line locations where it's used. + Ignores the integration test file itself, but will scan other test files. + """ + pattern = re.compile(r"domains(?:\.get\(\s*['\"](?P[^'\"]+)['\"]\s*\)|\[['\"](?P[^'\"]+)['\"]\])") + locations = {} + # Path of this test file to ignore + ignore_path = os.path.normpath(os.path.join(base_dir, 'tests', 'integration', 'test_domain_application_ids.py')) + + for root, dirs, files in os.walk(base_dir): + for fname in files: + # only scan YAML, YAML and Python files + if not fname.endswith(('.yml', '.yaml', '.py')): + continue + path = os.path.normpath(os.path.join(root, fname)) + # skip this integration test file + if path == ignore_path: + continue + try: + with open(path, 'r', encoding='utf-8') as f: + for lineno, line in enumerate(f, start=1): + for m in pattern.finditer(line): + key = m.group('id') or m.group('id2') + loc = f"{path}:{lineno}" + locations.setdefault(key, []).append(loc) + except (OSError, UnicodeDecodeError): + continue + return locations + + +class TestDomainApplicationIds(unittest.TestCase): + @classmethod + def setUpClass(cls): + # Load valid application IDs from roles + cls.valid_ids = set(get_all_application_ids(roles_dir='roles')) + if not cls.valid_ids: + raise RuntimeError("No application_ids found in roles/*/vars/main.yml") + # Collect domain keys and their locations, excluding this test file + cls.domain_locations = collect_domain_keys(base_dir='.') + if not cls.domain_locations: + raise RuntimeError("No domains.get(...) or domains[...] usages found to validate") + + # Define keys to ignore (placeholders or meta-fields) + cls.ignore_keys = {"canonical", "aliases"} + + def test_all_keys_are_valid(self): + """Ensure every domains.get/[] key matches a valid application_id (excluding ignored keys).""" + def is_placeholder(key): + # Treat keys with curly braces as placeholders + return bool(re.match(r"^\{.+\}$", key)) + + invalid = {} + for key, locs in self.domain_locations.items(): + if key in self.ignore_keys or is_placeholder(key): + continue + if key not in self.valid_ids: + invalid[key] = locs + + if invalid: + details = [] + for key, locs in invalid.items(): + locations_str = ", ".join(locs) + details.append(f"'{key}' at {locations_str}") + detail_msg = "; ".join(details) + self.fail( + f"Found usages of domains with invalid application_ids: {detail_msg}. " + f"Valid application_ids are: {sorted(self.valid_ids)}" + ) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/unit/filter_plugins/test_csp_filters.py b/tests/unit/filter_plugins/test_csp_filters.py index e4165302..cd5bfc72 100644 --- a/tests/unit/filter_plugins/test_csp_filters.py +++ b/tests/unit/filter_plugins/test_csp_filters.py @@ -179,7 +179,7 @@ class TestCspFilters(unittest.TestCase): # Ensure feature enabled and domain set self.apps['app1']['features']['portfolio_iframe'] = True # simulate a subdomain for the application - self.domains['portfolio'] = ['domain-example.com'] + self.domains['web-app-port-ui'] = ['domain-example.com'] header = self.filter.build_csp_header(self.apps, 'app1', self.domains, web_protocol='https') # Expect '*.domain-example.com' in the frame-ancestors directive