From 75cd372d0e4c00ad2a3df986ca6d02bc0577092f Mon Sep 17 00:00:00 2001 From: DL6ER Date: Sat, 4 Nov 2023 12:12:02 +0100 Subject: [PATCH] Add string format verification in API checker Signed-off-by: DL6ER --- src/api/docs/content/specs/config.yaml | 8 ++++ src/config/dnsmasq_config.c | 10 +++-- test/api/libs/responseVerifyer.py | 54 ++++++++++++++++++++------ 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/api/docs/content/specs/config.yaml b/src/api/docs/content/specs/config.yaml index 8158be03..59f24bf2 100644 --- a/src/api/docs/content/specs/config.yaml +++ b/src/api/docs/content/specs/config.yaml @@ -268,8 +268,10 @@ components: type: boolean IPv4: type: string + x-format: ipv4 IPv6: type: string + x-format: ipv6 blocking: type: object properties: @@ -279,8 +281,10 @@ components: type: boolean IPv4: type: string + x-format: ipv4 IPv6: type: string + x-format: ipv6 rateLimit: type: object properties: @@ -295,12 +299,16 @@ components: type: boolean start: type: string + x-format: ipv4 end: type: string + x-format: ipv4 router: type: string + x-format: ipv4 netmask: type: string + x-format: ipv4 domain: type: string leaseTime: diff --git a/src/config/dnsmasq_config.c b/src/config/dnsmasq_config.c index 21066558..3a3894af 100644 --- a/src/config/dnsmasq_config.c +++ b/src/config/dnsmasq_config.c @@ -419,9 +419,12 @@ bool __attribute__((const)) write_dnsmasq_config(struct config *conf, bool test_ fputs("# DHCP server setting\n", pihole_conf); fputs("dhcp-authoritative\n", pihole_conf); fputs("dhcp-leasefile="DHCPLEASESFILE"\n", pihole_conf); - char start[INET_ADDRSTRLEN] = { 0 }, end[INET_ADDRSTRLEN] = { 0 }; + char start[INET_ADDRSTRLEN] = { 0 }, + end[INET_ADDRSTRLEN] = { 0 }, + router[INET_ADDRSTRLEN] = { 0 }; inet_ntop(AF_INET, &conf->dhcp.start.v.in_addr, start, INET_ADDRSTRLEN); inet_ntop(AF_INET, &conf->dhcp.end.v.in_addr, end, INET_ADDRSTRLEN); + inet_ntop(AF_INET, &conf->dhcp.router.v.in_addr, router, INET_ADDRSTRLEN); fprintf(pihole_conf, "dhcp-range=%s,%s", start, end); // Net mask is optional, only add if it is not 0.0.0.0 const struct in_addr inaddr_empty = {0}; @@ -429,13 +432,12 @@ bool __attribute__((const)) write_dnsmasq_config(struct config *conf, bool test_ { char netmask[INET_ADDRSTRLEN] = { 0 }; inet_ntop(AF_INET, &conf->dhcp.netmask.v.in_addr, netmask, INET_ADDRSTRLEN); - fprintf(pihole_conf, ",%s", conf->dhcp.netmask.v.s); + fprintf(pihole_conf, ",%s", netmask); } // Lease time is optional, only add it if it is set if(strlen(conf->dhcp.leaseTime.v.s) > 0) fprintf(pihole_conf, ",%s", conf->dhcp.leaseTime.v.s); - fprintf(pihole_conf, "\ndhcp-option=option:router,%s\n", - conf->dhcp.router.v.s); + fprintf(pihole_conf, "\ndhcp-option=option:router,%s\n", router); if(conf->dhcp.rapidCommit.v.b) fputs("dhcp-rapid-commit\n", pihole_conf); diff --git a/test/api/libs/responseVerifyer.py b/test/api/libs/responseVerifyer.py index bc692fd0..c09d154a 100644 --- a/test/api/libs/responseVerifyer.py +++ b/test/api/libs/responseVerifyer.py @@ -10,7 +10,7 @@ # Please see LICENSE file for your rights under this license. import io -import pprint +import ipaddress import random import zipfile from libs.openAPI import openApi @@ -221,8 +221,32 @@ class ResponseVerifyer(): return self.errors + # Check if a string is a valid IPv4 address + def valid_ipv4(self, addr: str) -> bool: + octets = addr.split(".") # type: list[str] + if len(octets) != 4: + return False + for octet in octets: + if not octet.isdigit(): + return False + if int(octet) < 0 or int(octet) > 255: + return False + return True + + + # Check if a string is a valid IPv6 address + def valid_ipv6(self, addr: str) -> bool: + # Split the address into parts + parts = addr.split(":") # type: list[str] + # Check if the address is a valid IPv6 address + if len(parts) != 8: + return False + + # Verify a single property's type - def verify_type(self, prop_type: any, yaml_type: str, yaml_nullable: bool): + def verify_type(self, prop: any, yaml_type: str, yaml_nullable: bool, yaml_format: str = None): + # Get the type of the property + prop_type = type(prop) # None is an acceptable reply when this is specified in the API specs if prop_type is type(None) and yaml_nullable: return True @@ -230,6 +254,14 @@ class ResponseVerifyer(): if yaml_type not in self.YAML_TYPES: self.errors.append("Property type \"" + yaml_type + "\" is not valid in OpenAPI specs") return False + if yaml_format is not None: + # Check if the format is correct + if yaml_format == "ipv4" and not type(ipaddress.ip_address(prop)) is ipaddress.IPv4Address: + self.errors.append("Property \"" + str(prop) + "\" is not a valid IPv4 address") + return False + elif yaml_format == "ipv6" and not type(ipaddress.ip_address(prop)) is ipaddress.IPv6Address: + self.errors.append("Property \"" + str(prop) + "\" is not a valid IPv6 address") + return False return prop_type in self.YAML_TYPES[yaml_type] @@ -306,17 +338,19 @@ class ResponseVerifyer(): # if not defined as string, integer, etc.) yaml_nullable = 'nullable' in YAMLprop and YAMLprop['nullable'] == True + # Get format of this property (if defined) + yaml_format = YAMLprop['format'] if 'format' in YAMLprop else YAMLprop['x-format'] if 'x-format' in YAMLprop else None + # Add this property to the YAML response self.YAMLresponse[flat_path] = [] # Check type of YAML example (if defined) if 'example' in YAMLprop: - example_type = type(YAMLprop['example']) # Check if the type of the example matches the # type we defined in the API specs self.YAMLresponse[flat_path].append(YAMLprop['example']) - if not self.verify_type(example_type, yaml_type, yaml_nullable): - self.errors.append(f"API example ({str(example_type)}) does not match defined type ({yaml_type}) in {flat_path} (nullable: " + ("True" if yaml_nullable else "False") + ")") + if not self.verify_type(YAMLprop['example'], yaml_type, yaml_nullable, yaml_format): + self.errors.append(f"API example ({str(type(YAMLprop['example']))}) does not match defined type ({yaml_type}) in {flat_path} (nullable: " + ("True" if yaml_nullable else "False") + ")") return False # Check type of externally defined YAML examples (next to schema) @@ -340,16 +374,14 @@ class ResponseVerifyer(): if skip_this: continue # Check if the type of the example matches the type we defined in the API specs - example_type = type(example) self.YAMLresponse[flat_path].append(example) - if not self.verify_type(example_type, yaml_type, yaml_nullable): - self.errors.append(f"API example ({str(example_type)}) does not match defined type ({yaml_type}) in {flat_path} (nullable: " + ("True" if yaml_nullable else "False") + ")") + if not self.verify_type(example, yaml_type, yaml_nullable, yaml_format): + self.errors.append(f"API example ({str(type(example))}) does not match defined type ({yaml_type}) in {flat_path} (nullable: " + ("True" if yaml_nullable else "False") + ")") return False # Compare type of FTL's reply against what we defined in the API specs - ftl_type = type(FTLprop) - if not self.verify_type(ftl_type, yaml_type, yaml_nullable): - self.errors.append(f"FTL's reply ({str(ftl_type)}) does not match defined type ({yaml_type}) in {flat_path}") + if not self.verify_type(FTLprop, yaml_type, yaml_nullable, yaml_format): + self.errors.append(f"FTL's reply ({str(type(FTLprop))}) does not match defined type ({yaml_type}) in {flat_path}") return False return all_okay