diff --git a/boefjes/boefjes/plugins/kat_answer_parser/normalize.py b/boefjes/boefjes/plugins/kat_answer_parser/normalize.py index 7407a919109..c7a4cc31cd3 100644 --- a/boefjes/boefjes/plugins/kat_answer_parser/normalize.py +++ b/boefjes/boefjes/plugins/kat_answer_parser/normalize.py @@ -18,3 +18,13 @@ def run(normalizer_meta: NormalizerMeta, raw: bytes | str) -> Iterable[OOI]: bit_id="port-classification-ip", config=json.loads(raw), ) + + if "/bit/disallowed-csp-hostnames" in mime_types: + if isinstance(raw, bytes): + raw = raw.decode() + + yield Config( + ooi=normalizer_meta.raw_data.boefje_meta.input_ooi, + bit_id="disallowed-csp-hostnames", + config=json.loads(raw), + ) diff --git a/boefjes/boefjes/plugins/kat_kat_finding_types/kat_finding_types.json b/boefjes/boefjes/plugins/kat_kat_finding_types/kat_finding_types.json index b628ff66a97..c29a0b894d3 100644 --- a/boefjes/boefjes/plugins/kat_kat_finding_types/kat_finding_types.json +++ b/boefjes/boefjes/plugins/kat_kat_finding_types/kat_finding_types.json @@ -467,5 +467,11 @@ "risk": "low", "impact": "All Certificate Authorities may issue certificates for you domain.", "recommendation": "Set a CAA record to limit which CA's are allowed to issue certs." + }, + "KAT-DISALLOWED-DOMAIN-IN-CSP": { + "description": "This CSP header contains domains that are not allowed, If the website contains a cross-site scripting vulnerability, then JavaScript code can be injected into the web page hosted on these domains which can host files for anyone.", + "risk": "medium", + "impact": "Disallowed domains are domains that are for example 'world writable', this opens up the possibility for an atacker to host malicious files on a csp whitelisted domain.", + "recommendation": "Remove the offending hostname from the CSP header." } } diff --git a/octopoes/.ci/mock_bits/url_classification_mock/url_classification_mock.py b/octopoes/.ci/mock_bits/url_classification_mock/url_classification_mock.py index cfb3d594ebd..d32b4134c59 100644 --- a/octopoes/.ci/mock_bits/url_classification_mock/url_classification_mock.py +++ b/octopoes/.ci/mock_bits/url_classification_mock/url_classification_mock.py @@ -7,7 +7,7 @@ from octopoes.models.ooi.web import URL, HostnameHTTPURL, IPAddressHTTPURL, WebScheme -def run(url: URL, additional_oois: list, config: dict[str, str]) -> Iterator[OOI]: +def run(url: URL, additional_oois: list, config: dict) -> Iterator[OOI]: if url.raw.scheme == "http" or url.raw.scheme == "https": port = url.raw.port if port is None: diff --git a/octopoes/bits/ask_disallowed_domains/__init__.py b/octopoes/bits/ask_disallowed_domains/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/octopoes/bits/ask_disallowed_domains/ask_disallowed_domains.py b/octopoes/bits/ask_disallowed_domains/ask_disallowed_domains.py new file mode 100644 index 00000000000..b8aa105b6c8 --- /dev/null +++ b/octopoes/bits/ask_disallowed_domains/ask_disallowed_domains.py @@ -0,0 +1,21 @@ +import json +from collections.abc import Iterator +from pathlib import Path +from typing import Any + +from octopoes.models import OOI +from octopoes.models.ooi.network import Network +from octopoes.models.ooi.question import Question + + +def run( + input_ooi: Network, + additional_oois: list, + config: dict[str, Any], +) -> Iterator[OOI]: + network = input_ooi + + with (Path(__file__).parent / "question_schema.json").open() as f: + schema = json.load(f) + + yield Question(ooi=network.reference, schema_id=schema["$id"], json_schema=json.dumps(schema)) diff --git a/octopoes/bits/ask_disallowed_domains/bit.py b/octopoes/bits/ask_disallowed_domains/bit.py new file mode 100644 index 00000000000..a9fbdc02690 --- /dev/null +++ b/octopoes/bits/ask_disallowed_domains/bit.py @@ -0,0 +1,10 @@ +from bits.definitions import BitDefinition +from octopoes.models.ooi.network import Network + +BIT = BitDefinition( + id="ask-disallowed-domains", + consumes=Network, + parameters=[], + min_scan_level=0, + module="bits.ask_disallowed_domains.ask_disallowed_domains", +) diff --git a/octopoes/bits/ask_disallowed_domains/question_schema.json b/octopoes/bits/ask_disallowed_domains/question_schema.json new file mode 100644 index 00000000000..523538f53bd --- /dev/null +++ b/octopoes/bits/ask_disallowed_domains/question_schema.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "/bit/disallowed-csp-hostnames", + "type": "object", + "default": {}, + "Port Configuration": "Root Schema", + "required": [], + "properties": { + "disallowed_hostnames": { + "description": "Comma separated list of disallowed hostnames in CSP headers", + "type": "string", + "pattern": "^(\\s*(,*)[^,]+,?\\s*)*$", + "default": "github.com,google.com" + }, + "disallow_url_shorteners": { + "description": "Do you want to disallow url shorteners in csp headers?", + "type": "boolean", + "default": true + } + } +} diff --git a/octopoes/bits/ask_port_specification/ask_port_specification.py b/octopoes/bits/ask_port_specification/ask_port_specification.py index fddd78e3800..b8aa105b6c8 100644 --- a/octopoes/bits/ask_port_specification/ask_port_specification.py +++ b/octopoes/bits/ask_port_specification/ask_port_specification.py @@ -1,6 +1,7 @@ import json from collections.abc import Iterator from pathlib import Path +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.network import Network @@ -10,7 +11,7 @@ def run( input_ooi: Network, additional_oois: list, - config: dict[str, str], + config: dict[str, Any], ) -> Iterator[OOI]: network = input_ooi diff --git a/octopoes/bits/check_csp_header/check_csp_header.py b/octopoes/bits/check_csp_header/check_csp_header.py index aafe0466abc..d958afaa7ed 100644 --- a/octopoes/bits/check_csp_header/check_csp_header.py +++ b/octopoes/bits/check_csp_header/check_csp_header.py @@ -1,6 +1,7 @@ import ipaddress import re from collections.abc import Iterator +from typing import Any from octopoes.models import OOI, Reference from octopoes.models.ooi.findings import Finding, KATFindingType @@ -9,7 +10,7 @@ NON_DECIMAL_FILTER = re.compile(r"[^\d.]+") -def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, Any]) -> Iterator[OOI]: header = input_ooi if header.key.lower() != "content-security-policy": return diff --git a/octopoes/bits/check_cve_2021_41773/check_cve_2021_41773.py b/octopoes/bits/check_cve_2021_41773/check_cve_2021_41773.py index 6e3837391c1..c55e786b7fc 100644 --- a/octopoes/bits/check_cve_2021_41773/check_cve_2021_41773.py +++ b/octopoes/bits/check_cve_2021_41773/check_cve_2021_41773.py @@ -1,11 +1,12 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.findings import CVEFindingType, Finding from octopoes.models.types import HTTPHeader -def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, Any]) -> Iterator[OOI]: header = input_ooi if header.key.lower() != "server": return diff --git a/octopoes/bits/check_hsts_header/check_hsts_header.py b/octopoes/bits/check_hsts_header/check_hsts_header.py index fa6038c9291..d7e2dcf0c12 100644 --- a/octopoes/bits/check_hsts_header/check_hsts_header.py +++ b/octopoes/bits/check_hsts_header/check_hsts_header.py @@ -1,12 +1,13 @@ import datetime from collections.abc import Iterator +from typing import Any from octopoes.models import OOI, Reference from octopoes.models.ooi.findings import Finding, KATFindingType from octopoes.models.types import HTTPHeader -def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, Any]) -> Iterator[OOI]: header = input_ooi if header.key.lower() != "strict-transport-security": return diff --git a/octopoes/bits/default_findingtype_risk/default_findingtype_risk.py b/octopoes/bits/default_findingtype_risk/default_findingtype_risk.py index 27aa471d591..7acca28d705 100644 --- a/octopoes/bits/default_findingtype_risk/default_findingtype_risk.py +++ b/octopoes/bits/default_findingtype_risk/default_findingtype_risk.py @@ -1,10 +1,11 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.findings import FindingType, RiskLevelSeverity -def run(input_ooi: FindingType, additional_oois: list, config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: FindingType, additional_oois: list, config: dict[str, Any]) -> Iterator[OOI]: value_set = False if not input_ooi.risk_severity: input_ooi.risk_severity = RiskLevelSeverity.PENDING diff --git a/octopoes/bits/disallowed_csp_hostnames/__init__.py b/octopoes/bits/disallowed_csp_hostnames/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/octopoes/bits/disallowed_csp_hostnames/bit.py b/octopoes/bits/disallowed_csp_hostnames/bit.py new file mode 100644 index 00000000000..73d1a2fbc99 --- /dev/null +++ b/octopoes/bits/disallowed_csp_hostnames/bit.py @@ -0,0 +1,10 @@ +from bits.definitions import BitDefinition +from octopoes.models.ooi.web import HTTPHeaderHostname + +BIT = BitDefinition( + id="disallowed-csp-hostnames", + consumes=HTTPHeaderHostname, + parameters=[], + module="bits.disallowed_csp_hostnames.disallowed_csp_hostnames", + config_ooi_relation_path="HTTPHeaderHostname.hostname.network", +) diff --git a/octopoes/bits/disallowed_csp_hostnames/disallowed_csp_hostnames.py b/octopoes/bits/disallowed_csp_hostnames/disallowed_csp_hostnames.py new file mode 100644 index 00000000000..9df38e24c13 --- /dev/null +++ b/octopoes/bits/disallowed_csp_hostnames/disallowed_csp_hostnames.py @@ -0,0 +1,45 @@ +import logging +from collections.abc import Iterator +from typing import Any + +from link_shorteners import link_shorteners_list + +from octopoes.models import OOI +from octopoes.models.ooi.findings import Finding, KATFindingType +from octopoes.models.ooi.web import HTTPHeaderHostname + +LINK_SHORTENERS = link_shorteners_list() + +logger = logging.getLogger(__name__) + + +def get_disallowed_hostnames_from_config(config, config_key, default): + disallowed_hostnames = config.get(config_key, None) + if disallowed_hostnames is None: + return default + return list(disallowed_hostnames.strip().split(",")) if disallowed_hostnames else [] + + +def run(input_ooi: HTTPHeaderHostname, additional_oois: list, config: dict[str, Any]) -> Iterator[OOI]: + header_hostname = input_ooi + header = header_hostname.header + + if header.tokenized.key.lower() != "content-security-policy": + return + + disallow_url_shorteners = config.get("disallow_url_shorteners", True) if config else True + + hostname = header_hostname.hostname.tokenized.name + disallowed_domains = link_shorteners_list() if disallow_url_shorteners else [] + disallowed_hostnames_from_config = get_disallowed_hostnames_from_config(config, "disallowed_hostnames", []) + + disallowed_domains.extend(disallowed_hostnames_from_config) + + if hostname.lower() in disallowed_domains: + ft = KATFindingType(id="KAT-DISALLOWED-DOMAIN-IN-CSP") + f = Finding( + ooi=input_ooi.reference, + finding_type=ft.reference, + ) + yield ft + yield f diff --git a/octopoes/bits/dns_alias_resolving/dns_alias_resolving.py b/octopoes/bits/dns_alias_resolving/dns_alias_resolving.py index e23ded9dab4..8316eb78f8a 100644 --- a/octopoes/bits/dns_alias_resolving/dns_alias_resolving.py +++ b/octopoes/bits/dns_alias_resolving/dns_alias_resolving.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.dns.records import DNSCNAMERecord @@ -7,7 +8,7 @@ def run( - hostname: Hostname, additional_oois: list[DNSCNAMERecord | ResolvedHostname], config: dict[str, str] + hostname: Hostname, additional_oois: list[DNSCNAMERecord | ResolvedHostname], config: dict[str, Any] ) -> Iterator[OOI]: cname_records = [ooi for ooi in additional_oois if isinstance(ooi, DNSCNAMERecord)] resolved_hostnames = [ooi for ooi in additional_oois if isinstance(ooi, ResolvedHostname)] diff --git a/octopoes/bits/dns_resolving/dns_resolving.py b/octopoes/bits/dns_resolving/dns_resolving.py index 1722ac3254a..6083284de24 100644 --- a/octopoes/bits/dns_resolving/dns_resolving.py +++ b/octopoes/bits/dns_resolving/dns_resolving.py @@ -1,11 +1,12 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.dns.records import DNSAAAARecord, DNSARecord from octopoes.models.ooi.dns.zone import Hostname, ResolvedHostname -def run(hostname: Hostname, additional_oois: list[DNSARecord | DNSAAAARecord], config: dict[str, str]) -> Iterator[OOI]: +def run(hostname: Hostname, additional_oois: list[DNSARecord | DNSAAAARecord], config: dict[str, Any]) -> Iterator[OOI]: for record in additional_oois: yield ResolvedHostname( hostname=hostname.reference, diff --git a/octopoes/bits/expiring_certificate/expiring_certificate.py b/octopoes/bits/expiring_certificate/expiring_certificate.py index 5224d632209..5ec4278776c 100644 --- a/octopoes/bits/expiring_certificate/expiring_certificate.py +++ b/octopoes/bits/expiring_certificate/expiring_certificate.py @@ -1,5 +1,6 @@ import datetime from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.certificate import X509Certificate @@ -9,7 +10,7 @@ THRESHOLD = datetime.timedelta(weeks=2) -def run(input_ooi: X509Certificate, additional_oois: list[Website], config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: X509Certificate, additional_oois: list[Website], config: dict[str, Any]) -> Iterator[OOI]: # only applies to OOIs referencing the certificate if input_ooi.expired: ft = KATFindingType(id="KAT-CERTIFICATE-EXPIRED") diff --git a/octopoes/bits/https_availability/https_availability.py b/octopoes/bits/https_availability/https_availability.py index e68795672c8..2abec18b340 100644 --- a/octopoes/bits/https_availability/https_availability.py +++ b/octopoes/bits/https_availability/https_availability.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.findings import Finding, KATFindingType @@ -6,7 +7,7 @@ from octopoes.models.ooi.web import Website -def run(input_ooi: IPAddress, additional_oois: list[IPPort | Website], config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: IPAddress, additional_oois: list[IPPort | Website], config: dict[str, Any]) -> Iterator[OOI]: websites = [website for website in additional_oois if isinstance(website, Website)] open_ports = [port.port for port in additional_oois if isinstance(port, IPPort)] diff --git a/octopoes/bits/https_redirect/https_redirect.py b/octopoes/bits/https_redirect/https_redirect.py index 3be8c16b695..86a0d66cb3f 100644 --- a/octopoes/bits/https_redirect/https_redirect.py +++ b/octopoes/bits/https_redirect/https_redirect.py @@ -1,11 +1,12 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.findings import Finding, KATFindingType from octopoes.models.ooi.web import HostnameHTTPURL, HTTPHeader -def run(input_ooi: HostnameHTTPURL, additional_oois: list[HTTPHeader], config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: HostnameHTTPURL, additional_oois: list[HTTPHeader], config: dict[str, Any]) -> Iterator[OOI]: header_keys = [header.key.lower() for header in additional_oois if isinstance(header, HTTPHeader)] # only check for http urls diff --git a/octopoes/bits/internetnl/internetnl.py b/octopoes/bits/internetnl/internetnl.py index 7190a96981a..a0058b22fc8 100644 --- a/octopoes/bits/internetnl/internetnl.py +++ b/octopoes/bits/internetnl/internetnl.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.dns.zone import Hostname @@ -6,7 +7,7 @@ from octopoes.models.ooi.web import Website -def run(input_ooi: Hostname, additional_oois: list[Finding | Website], config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: Hostname, additional_oois: list[Finding | Website], config: dict[str, Any]) -> Iterator[OOI]: # only websites have to comply with the internetnl rules websites = [websites for websites in additional_oois if isinstance(websites, Website)] if not websites: diff --git a/octopoes/bits/ipv6_nameservers/ipv6_nameservers.py b/octopoes/bits/ipv6_nameservers/ipv6_nameservers.py index 9f2ff672ded..f4bbd807011 100644 --- a/octopoes/bits/ipv6_nameservers/ipv6_nameservers.py +++ b/octopoes/bits/ipv6_nameservers/ipv6_nameservers.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.dns.records import DNSAAAARecord, DNSARecord, DNSNSRecord @@ -6,7 +7,7 @@ from octopoes.models.ooi.findings import Finding, KATFindingType -def run(hostname: Hostname, additional_oois: list[DNSAAAARecord | DNSARecord], config: dict[str, str]) -> Iterator[OOI]: +def run(hostname: Hostname, additional_oois: list[DNSAAAARecord | DNSARecord], config: dict[str, Any]) -> Iterator[OOI]: dns_ns_records = [dns_ns_record for dns_ns_record in additional_oois if isinstance(dns_ns_record, DNSNSRecord)] dns_aaaa_records = [ dns_aaaa_record for dns_aaaa_record in additional_oois if isinstance(dns_aaaa_record, DNSAAAARecord) diff --git a/octopoes/bits/ipv6_webservers/ipv6_webservers.py b/octopoes/bits/ipv6_webservers/ipv6_webservers.py index 24019a5c160..bc4a2b547f4 100644 --- a/octopoes/bits/ipv6_webservers/ipv6_webservers.py +++ b/octopoes/bits/ipv6_webservers/ipv6_webservers.py @@ -7,7 +7,7 @@ def run( - hostname: Hostname, additional_oois: list[DNSAAAARecord | DNSARecord | DNSNSRecord], config: dict[str, str] + hostname: Hostname, additional_oois: list[DNSAAAARecord | DNSARecord | DNSNSRecord], config: dict ) -> Iterator[OOI]: dns_a_records = [dns_a_record for dns_a_record in additional_oois if isinstance(dns_a_record, DNSARecord)] dns_aaaa_records = [ diff --git a/octopoes/bits/missing_caa/missing_caa.py b/octopoes/bits/missing_caa/missing_caa.py index 94515d57a87..b08b7e391df 100644 --- a/octopoes/bits/missing_caa/missing_caa.py +++ b/octopoes/bits/missing_caa/missing_caa.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any import tldextract @@ -8,7 +9,7 @@ from octopoes.models.ooi.findings import Finding, KATFindingType -def run(input_ooi: Hostname, additional_oois: list[DNSCAARecord | NXDOMAIN], config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: Hostname, additional_oois: list[DNSCAARecord | NXDOMAIN], config: dict[str, Any]) -> Iterator[OOI]: caa_records = [ooi for ooi in additional_oois if isinstance(ooi, DNSCAARecord)] nxdomains = (ooi for ooi in additional_oois if isinstance(ooi, NXDOMAIN)) diff --git a/octopoes/bits/missing_certificate/missing_certificate.py b/octopoes/bits/missing_certificate/missing_certificate.py index 998cc452a12..04721d51dc6 100644 --- a/octopoes/bits/missing_certificate/missing_certificate.py +++ b/octopoes/bits/missing_certificate/missing_certificate.py @@ -1,11 +1,12 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.findings import Finding, KATFindingType from octopoes.models.ooi.web import Website -def run(input_ooi: Website, additional_oois, config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: Website, additional_oois, config: dict[str, Any]) -> Iterator[OOI]: if input_ooi.ip_service.tokenized.service.name.lower() != "https": return diff --git a/octopoes/bits/missing_dkim/missing_dkim.py b/octopoes/bits/missing_dkim/missing_dkim.py index d9228f34963..f38740423a5 100644 --- a/octopoes/bits/missing_dkim/missing_dkim.py +++ b/octopoes/bits/missing_dkim/missing_dkim.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any import tldextract @@ -9,7 +10,7 @@ from octopoes.models.ooi.findings import Finding, KATFindingType -def run(input_ooi: Hostname, additional_oois: list[DKIMExists | NXDOMAIN], config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: Hostname, additional_oois: list[DKIMExists | NXDOMAIN], config: dict[str, Any]) -> Iterator[OOI]: dkim_exists = [ooi for ooi in additional_oois if isinstance(ooi, DKIMExists)] nxdomains = (ooi for ooi in additional_oois if isinstance(ooi, NXDOMAIN)) diff --git a/octopoes/bits/missing_dmarc/missing_dmarc.py b/octopoes/bits/missing_dmarc/missing_dmarc.py index 49ff1954063..865c59b99e9 100644 --- a/octopoes/bits/missing_dmarc/missing_dmarc.py +++ b/octopoes/bits/missing_dmarc/missing_dmarc.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any import tldextract @@ -9,7 +10,7 @@ from octopoes.models.ooi.findings import Finding, KATFindingType -def run(input_ooi: Hostname, additional_oois: list[DMARCTXTRecord | NXDOMAIN], config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: Hostname, additional_oois: list[DMARCTXTRecord | NXDOMAIN], config: dict[str, Any]) -> Iterator[OOI]: dmarc_records = [ooi for ooi in additional_oois if isinstance(ooi, DMARCTXTRecord)] nxdomains = (ooi for ooi in additional_oois if isinstance(ooi, NXDOMAIN)) diff --git a/octopoes/bits/missing_headers/missing_headers.py b/octopoes/bits/missing_headers/missing_headers.py index 9c676fe5183..b0c75045e15 100644 --- a/octopoes/bits/missing_headers/missing_headers.py +++ b/octopoes/bits/missing_headers/missing_headers.py @@ -1,11 +1,12 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.findings import Finding, KATFindingType from octopoes.models.ooi.web import HTTPHeader, HTTPResource -def run(resource: HTTPResource, additional_oois: list[HTTPHeader], config: dict[str, str]) -> Iterator[OOI]: +def run(resource: HTTPResource, additional_oois: list[HTTPHeader], config: dict[str, Any]) -> Iterator[OOI]: if not additional_oois: return diff --git a/octopoes/bits/missing_spf/missing_spf.py b/octopoes/bits/missing_spf/missing_spf.py index 07613d9872e..d40e368edd4 100644 --- a/octopoes/bits/missing_spf/missing_spf.py +++ b/octopoes/bits/missing_spf/missing_spf.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any import tldextract @@ -9,7 +10,7 @@ from octopoes.models.ooi.findings import Finding, KATFindingType -def run(input_ooi: Hostname, additional_oois: list[DNSSPFRecord | NXDOMAIN], config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: Hostname, additional_oois: list[DNSSPFRecord | NXDOMAIN], config: dict[str, Any]) -> Iterator[OOI]: spf_records = [ooi for ooi in additional_oois if isinstance(ooi, DNSSPFRecord)] nxdomains = (ooi for ooi in additional_oois if isinstance(ooi, NXDOMAIN)) diff --git a/octopoes/bits/nxdomain_flag/nxdomain_flag.py b/octopoes/bits/nxdomain_flag/nxdomain_flag.py index 6cbc7942897..b42685d9210 100644 --- a/octopoes/bits/nxdomain_flag/nxdomain_flag.py +++ b/octopoes/bits/nxdomain_flag/nxdomain_flag.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.dns.zone import Hostname @@ -6,7 +7,7 @@ from octopoes.models.types import NXDOMAIN -def run(input_ooi: Hostname, additional_oois: list[NXDOMAIN], config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: Hostname, additional_oois: list[NXDOMAIN], config: dict[str, Any]) -> Iterator[OOI]: if additional_oois: nxdomain = KATFindingType(id="KAT-NXDOMAIN") yield nxdomain diff --git a/octopoes/bits/nxdomain_header_flag/nxdomain_header_flag.py b/octopoes/bits/nxdomain_header_flag/nxdomain_header_flag.py index ca7298b41d5..f55c6b54561 100644 --- a/octopoes/bits/nxdomain_header_flag/nxdomain_header_flag.py +++ b/octopoes/bits/nxdomain_header_flag/nxdomain_header_flag.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.dns.zone import Hostname @@ -8,7 +9,7 @@ def run( - input_ooi: Hostname, additional_oois: list[NXDOMAIN | HTTPHeaderHostname], config: dict[str, str] + input_ooi: Hostname, additional_oois: list[NXDOMAIN | HTTPHeaderHostname], config: dict[str, Any] ) -> Iterator[OOI]: hostname_exists = True headers = [] diff --git a/octopoes/bits/oois_in_headers/oois_in_headers.py b/octopoes/bits/oois_in_headers/oois_in_headers.py index 7988c1e0220..a6e67d15b08 100644 --- a/octopoes/bits/oois_in_headers/oois_in_headers.py +++ b/octopoes/bits/oois_in_headers/oois_in_headers.py @@ -1,5 +1,6 @@ import re from collections.abc import Iterator +from typing import Any from urllib.parse import urljoin, urlparse from pydantic import ValidationError @@ -15,7 +16,7 @@ def is_url(input_str): return bool(result.scheme) -def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: HTTPHeader, additional_oois: list, config: dict[str, Any]) -> Iterator[OOI]: network = Network(name="internet") if input_ooi.key.lower() == "location": diff --git a/octopoes/bits/port_classification_ip/port_classification_ip.py b/octopoes/bits/port_classification_ip/port_classification_ip.py index 5b7fbd317e3..c1652a5e7eb 100644 --- a/octopoes/bits/port_classification_ip/port_classification_ip.py +++ b/octopoes/bits/port_classification_ip/port_classification_ip.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.findings import Finding, KATFindingType @@ -44,7 +45,7 @@ def get_ports_from_config(config, config_key, default): return list(map(int, ports.split(","))) if ports else [] -def run(input_ooi: IPPort, additional_oois: list, config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: IPPort, additional_oois: list, config: dict[str, Any]) -> Iterator[OOI]: aggregate_findings = config.get("aggregate_findings", "False").lower() == "true" if config else False open_ports = [] diff --git a/octopoes/bits/port_common/port_common.py b/octopoes/bits/port_common/port_common.py index 6d660903257..141182dec90 100644 --- a/octopoes/bits/port_common/port_common.py +++ b/octopoes/bits/port_common/port_common.py @@ -25,7 +25,7 @@ def run( input_ooi: IPPort, additional_oois: list, - config: dict[str, str], + config: dict, ) -> Iterator[OOI]: port = input_ooi.port protocol = input_ooi.protocol diff --git a/octopoes/bits/resource_discovery/resource_discovery.py b/octopoes/bits/resource_discovery/resource_discovery.py index 6b3a3cc7406..86a496d74c3 100644 --- a/octopoes/bits/resource_discovery/resource_discovery.py +++ b/octopoes/bits/resource_discovery/resource_discovery.py @@ -1,11 +1,12 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.dns.zone import Hostname from octopoes.models.ooi.web import HostnameHTTPURL, HTTPResource, Website -def run(hostname: Hostname, additional_oois: list[HostnameHTTPURL | Website], config: dict[str, str]) -> Iterator[OOI]: +def run(hostname: Hostname, additional_oois: list[HostnameHTTPURL | Website], config: dict[str, Any]) -> Iterator[OOI]: hostname_http_urls = [ hostname_http_url for hostname_http_url in additional_oois if isinstance(hostname_http_url, HostnameHTTPURL) ] diff --git a/octopoes/bits/retire_js/retire_js.py b/octopoes/bits/retire_js/retire_js.py index 9cd42f98089..5b57382ffc4 100644 --- a/octopoes/bits/retire_js/retire_js.py +++ b/octopoes/bits/retire_js/retire_js.py @@ -2,6 +2,7 @@ import json from collections.abc import Iterator from pathlib import Path +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.findings import CVEFindingType, Finding, RetireJSFindingType @@ -9,7 +10,7 @@ from packaging import version -def run(input_ooi: Software, additional_oois: list[SoftwareInstance], config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: Software, additional_oois: list[SoftwareInstance], config: dict[str, Any]) -> Iterator[OOI]: software_name = input_ooi.name software_version = input_ooi.version if input_ooi.version else "999.9.9" diff --git a/octopoes/bits/runner.py b/octopoes/bits/runner.py index 4a9da52f2f9..da851076c34 100644 --- a/octopoes/bits/runner.py +++ b/octopoes/bits/runner.py @@ -35,7 +35,7 @@ def __str__(self): return f"BitRunner {self.module}" -def _bit_run_signature(input_ooi: OOI, additional_oois: list[OOI], config: dict[str, str]) -> Iterator[OOI]: +def _bit_run_signature(input_ooi: OOI, additional_oois: list[OOI], config: dict[str, Any]) -> Iterator[OOI]: yield input_ooi diff --git a/octopoes/bits/spf_discovery/spf_discovery.py b/octopoes/bits/spf_discovery/spf_discovery.py index 74cdd131781..36a10f16003 100644 --- a/octopoes/bits/spf_discovery/spf_discovery.py +++ b/octopoes/bits/spf_discovery/spf_discovery.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any from bits.spf_discovery.internetnl_spf_parser import parse from octopoes.models import OOI @@ -9,7 +10,7 @@ from octopoes.models.ooi.network import IPAddressV4, IPAddressV6, Network -def run(input_ooi: DNSTXTRecord, additional_oois, config: dict[str, str]) -> Iterator[OOI]: +def run(input_ooi: DNSTXTRecord, additional_oois, config: dict[str, Any]) -> Iterator[OOI]: if input_ooi.value.startswith("v=spf1"): spf_value = input_ooi.value.replace("%(d)", input_ooi.hostname.tokenized.name) parsed = parse(spf_value) diff --git a/octopoes/bits/ssl_certificate_hostname/ssl_certificate_hostname.py b/octopoes/bits/ssl_certificate_hostname/ssl_certificate_hostname.py index 88c23bccc84..8fb34ade12f 100644 --- a/octopoes/bits/ssl_certificate_hostname/ssl_certificate_hostname.py +++ b/octopoes/bits/ssl_certificate_hostname/ssl_certificate_hostname.py @@ -24,7 +24,7 @@ def hostname_in_qualifiers(hostname: str, qualifiers: list[str]) -> bool: def run( input_ooi: X509Certificate, additional_oois: list[Website | SubjectAlternativeNameHostname], - config: dict[str, str], + config: dict, ) -> Iterator[OOI]: websites = [website for website in additional_oois if isinstance(website, Website)] subject_alternative_name_hostnames = [ diff --git a/octopoes/bits/two_ipv6_nameservers/two_ipv6_nameservers.py b/octopoes/bits/two_ipv6_nameservers/two_ipv6_nameservers.py index 4002c5d2d1a..ec555a55157 100644 --- a/octopoes/bits/two_ipv6_nameservers/two_ipv6_nameservers.py +++ b/octopoes/bits/two_ipv6_nameservers/two_ipv6_nameservers.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.dns.records import DNSNSRecord @@ -6,7 +7,7 @@ from octopoes.models.ooi.findings import Finding, KATFindingType -def run(hostname: Hostname, additional_oois: list[Finding | DNSNSRecord], config: dict[str, str]) -> Iterator[OOI]: +def run(hostname: Hostname, additional_oois: list[Finding | DNSNSRecord], config: dict[str, Any]) -> Iterator[OOI]: no_ipv6_findings = [ finding for finding in additional_oois diff --git a/octopoes/bits/url_classification/url_classification.py b/octopoes/bits/url_classification/url_classification.py index cfb3d594ebd..74ebc7a0aff 100644 --- a/octopoes/bits/url_classification/url_classification.py +++ b/octopoes/bits/url_classification/url_classification.py @@ -1,5 +1,6 @@ from collections.abc import Iterator from ipaddress import IPv4Address, ip_address +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.dns.zone import Hostname @@ -7,7 +8,7 @@ from octopoes.models.ooi.web import URL, HostnameHTTPURL, IPAddressHTTPURL, WebScheme -def run(url: URL, additional_oois: list, config: dict[str, str]) -> Iterator[OOI]: +def run(url: URL, additional_oois: list, config: dict[str, Any]) -> Iterator[OOI]: if url.raw.scheme == "http" or url.raw.scheme == "https": port = url.raw.port if port is None: diff --git a/octopoes/bits/url_discovery/url_discovery.py b/octopoes/bits/url_discovery/url_discovery.py index 3b2142ad4f4..4cf45d7042d 100644 --- a/octopoes/bits/url_discovery/url_discovery.py +++ b/octopoes/bits/url_discovery/url_discovery.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.dns.zone import ResolvedHostname @@ -7,7 +8,7 @@ def run( - ip_address: IPAddress, additional_oois: list[IPPort | ResolvedHostname], config: dict[str, str] + ip_address: IPAddress, additional_oois: list[IPPort | ResolvedHostname], config: dict[str, Any] ) -> Iterator[OOI]: hostnames = [resolved.hostname for resolved in additional_oois if isinstance(resolved, ResolvedHostname)] ip_ports = [ip_port for ip_port in additional_oois if isinstance(ip_port, IPPort)] diff --git a/octopoes/bits/website_discovery/website_discovery.py b/octopoes/bits/website_discovery/website_discovery.py index 4bdbd2f87b1..068e3672226 100644 --- a/octopoes/bits/website_discovery/website_discovery.py +++ b/octopoes/bits/website_discovery/website_discovery.py @@ -1,4 +1,5 @@ from collections.abc import Iterator +from typing import Any from octopoes.models import OOI from octopoes.models.ooi.dns.zone import ResolvedHostname @@ -8,7 +9,7 @@ def run( - ip_address: IPAddressV4, additional_oois: list[IPService | ResolvedHostname], config: dict[str, str] + ip_address: IPAddressV4, additional_oois: list[IPService | ResolvedHostname], config: dict[str, Any] ) -> Iterator[OOI]: def is_service_http(ip_service: IPService) -> bool: return "http" in ip_service.service.tokenized.name.lower().strip() diff --git a/octopoes/octopoes/models/ooi/config.py b/octopoes/octopoes/models/ooi/config.py index 196da96a0a0..c9709cf2f0c 100644 --- a/octopoes/octopoes/models/ooi/config.py +++ b/octopoes/octopoes/models/ooi/config.py @@ -1,4 +1,4 @@ -from typing import Literal +from typing import Any, Literal from octopoes.models import OOI, Reference from octopoes.models.persistence import ReferenceField @@ -9,7 +9,7 @@ class Config(OOI): ooi: Reference = ReferenceField(OOI) bit_id: str - config: dict[str, str] + config: dict[str, Any] _natural_key_attrs = ["ooi", "bit_id"] diff --git a/octopoes/poetry.lock b/octopoes/poetry.lock index b240f1bc25a..8351c3a26c7 100644 --- a/octopoes/poetry.lock +++ b/octopoes/poetry.lock @@ -850,6 +850,16 @@ sqs = ["boto3 (>=1.26.143)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"] yaml = ["PyYAML (>=3.10)"] zookeeper = ["kazoo (>=2.8.0)"] +[[package]] +name = "link-shorteners" +version = "1.10.0" +description = "List of all URL shorteners, such as bitly, tinyurl, shorturl, and many others." +optional = false +python-versions = "*" +files = [ + {file = "link-shorteners-1.10.0.tar.gz", hash = "sha256:e13c16c1153b5e40be9e3ed6653b8e196041c3f531bfc9cff7f0fcd1b5a77b0a"}, +] + [[package]] name = "opentelemetry-api" version = "1.23.0" @@ -2081,4 +2091,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "43ed837068ec152f98518aaa8d1e639c6c6cbdf34950310b5165fd69d9ba333c" +content-hash = "37b78776591ad6950d689b7416cb634d25ab8d8f1646ac95948b1136372736a4" diff --git a/octopoes/pyproject.toml b/octopoes/pyproject.toml index f4d6801e52c..7affc698ff6 100644 --- a/octopoes/pyproject.toml +++ b/octopoes/pyproject.toml @@ -23,15 +23,17 @@ pyparsing = "^3.0.9" packaging = "^23.0" tldextract = "^3.4.0" opentelemetry-sdk = "^1.23.0" +opentelemetry-instrumentation = "^0.44b0" opentelemetry-exporter-otlp-proto-grpc = "^1.23.0" opentelemetry-instrumentation-fastapi = "^0.44b0" +opentelemetry-instrumentation-psycopg2 = "^0.44b0" +opentelemetry-instrumentation-httpx = "^0.44b0" sqlalchemy = "1.4.48" jsonschema = "^4.18.0" -opentelemetry-instrumentation = "^0.44b0" -opentelemetry-instrumentation-psycopg2 = "^0.44b0" pydantic-settings = "^2.0.3" httpx = "^0.27.0" -opentelemetry-instrumentation-httpx = "^0.44b0" +# required by disallowed-csp-hostnames bit +link-shorteners = "^1.0.7" [tool.poetry.group.dev.dependencies] robotframework = "^7.0" diff --git a/octopoes/requirements-dev.txt b/octopoes/requirements-dev.txt index d0b82c6b8cf..9fba3435c71 100644 --- a/octopoes/requirements-dev.txt +++ b/octopoes/requirements-dev.txt @@ -349,6 +349,8 @@ jsonschema==4.21.1 ; python_version >= "3.10" and python_version < "4.0" \ kombu==5.3.5 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:0eac1bbb464afe6fb0924b21bf79460416d25d8abc52546d4f16cad94f789488 \ --hash=sha256:30e470f1a6b49c70dc6f6d13c3e4cc4e178aa6c469ceb6bcd55645385fc84b93 +link-shorteners==1.10.0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:e13c16c1153b5e40be9e3ed6653b8e196041c3f531bfc9cff7f0fcd1b5a77b0a opentelemetry-api==1.23.0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:14a766548c8dd2eb4dfc349739eb4c3893712a0daa996e5dbf945f9da665da9d \ --hash=sha256:cc03ea4025353048aadb9c64919099663664672ea1c6be6ddd8fee8e4cd5e774 diff --git a/octopoes/requirements.txt b/octopoes/requirements.txt index f9c8197d84d..b25ca7f5b18 100644 --- a/octopoes/requirements.txt +++ b/octopoes/requirements.txt @@ -284,6 +284,8 @@ jsonschema==4.21.1 ; python_version >= "3.10" and python_version < "4.0" \ kombu==5.3.5 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:0eac1bbb464afe6fb0924b21bf79460416d25d8abc52546d4f16cad94f789488 \ --hash=sha256:30e470f1a6b49c70dc6f6d13c3e4cc4e178aa6c469ceb6bcd55645385fc84b93 +link-shorteners==1.10.0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:e13c16c1153b5e40be9e3ed6653b8e196041c3f531bfc9cff7f0fcd1b5a77b0a opentelemetry-api==1.23.0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:14a766548c8dd2eb4dfc349739eb4c3893712a0daa996e5dbf945f9da665da9d \ --hash=sha256:cc03ea4025353048aadb9c64919099663664672ea1c6be6ddd8fee8e4cd5e774 diff --git a/octopoes/tests/integration/test_ooi_deletion.py b/octopoes/tests/integration/test_ooi_deletion.py index 0eab85b75ae..ad52fbe1b98 100644 --- a/octopoes/tests/integration/test_ooi_deletion.py +++ b/octopoes/tests/integration/test_ooi_deletion.py @@ -134,9 +134,9 @@ def test_events_created_in_worker_during_handling( xtdb_octopoes_service.process_event(event) xtdb_octopoes_service.commit() - assert len(event_manager.queue) == 6 # Handling OOI delete event triggers Origin delete event + assert len(event_manager.queue) == 7 # Handling OOI delete event triggers Origin delete event - event = event_manager.queue[5] # OOID]elete event + event = event_manager.queue[6] # OOID]elete event assert isinstance(event, OriginDBEvent) assert event.operation_type.value == "delete" @@ -232,7 +232,7 @@ def test_deletion_events_after_nxdomain( event_manager.complete_process_events(xtdb_octopoes_service) assert len(list(filter(lambda x: x.operation_type.value == "delete", event_manager.queue))) == 0 - assert xtdb_octopoes_service.ooi_repository.list_oois({OOI}, valid_time).count == 6 + assert xtdb_octopoes_service.ooi_repository.list_oois({OOI}, valid_time).count == 7 nxd = NXDOMAIN(hostname=hostname.reference) xtdb_octopoes_service.ooi_repository.save(nxd, valid_time) @@ -253,7 +253,7 @@ def test_deletion_events_after_nxdomain( event_manager.complete_process_events(xtdb_octopoes_service) assert len(list(filter(lambda x: x.operation_type.value == "delete", event_manager.queue))) >= 3 - assert xtdb_octopoes_service.ooi_repository.list_oois({OOI}, valid_time).count == 4 + assert xtdb_octopoes_service.ooi_repository.list_oois({OOI}, valid_time).count == 5 @pytest.mark.xfail(reason="Wappalyzer works on wrong input objects (to be addressed)") diff --git a/octopoes/tests/test_disallowed_csp_hostnames.py b/octopoes/tests/test_disallowed_csp_hostnames.py new file mode 100644 index 00000000000..60abf4b38c5 --- /dev/null +++ b/octopoes/tests/test_disallowed_csp_hostnames.py @@ -0,0 +1,67 @@ +from bits.disallowed_csp_hostnames.disallowed_csp_hostnames import run + +from octopoes.models import Reference +from octopoes.models.ooi.findings import Finding, KATFindingType +from octopoes.models.ooi.web import HTTPHeaderHostname + + +def test_disallowed_csp_headers_no_findings(): + http_header_hostname = HTTPHeaderHostname( + hostname=Reference.from_str("Hostname|internet|example.com"), + header=Reference.from_str( + "HTTPHeader|internet|1.1.1.1|tcp|443|https|internet|example.com|https|internet|example.com|443||Content-Security-Policy" + ), + ) + + results = list(run(http_header_hostname, [], {})) + + assert results == [] + + +def test_disallowed_csp_headers_simple_finding(): + http_header_hostname = HTTPHeaderHostname( + hostname=Reference.from_str("Hostname|internet|bit.ly"), + header=Reference.from_str( + "HTTPHeader|internet|1.1.1.1|tcp|443|https|internet|example.com|https|internet|example.com|443||Content-Security-Policy" + ), + ) + + results = list(run(http_header_hostname, [], {})) + + assert results == [ + KATFindingType(id="KAT-DISALLOWED-DOMAIN-IN-CSP"), + Finding( + ooi=http_header_hostname.reference, finding_type=KATFindingType(id="KAT-DISALLOWED-DOMAIN-IN-CSP").reference + ), + ] + + +def test_disallowed_csp_headers_allow_url_shortener(): + http_header_hostname = HTTPHeaderHostname( + hostname=Reference.from_str("Hostname|internet|bit.ly"), + header=Reference.from_str( + "HTTPHeader|internet|1.1.1.1|tcp|443|https|internet|example.com|https|internet|example.com|443||Content-Security-Policy" + ), + ) + + results = list(run(http_header_hostname, [], {"disallow_url_shorteners": False})) + + assert results == [] + + +def test_disallowed_csp_headers_disallow_custom_hostname(): + http_header_hostname = HTTPHeaderHostname( + hostname=Reference.from_str("Hostname|internet|example.com"), + header=Reference.from_str( + "HTTPHeader|internet|1.1.1.1|tcp|443|https|internet|example.com|https|internet|example.com|443||Content-Security-Policy" + ), + ) + + results = list(run(http_header_hostname, [], {"disallowed_hostnames": "example.com"})) + + assert results == [ + KATFindingType(id="KAT-DISALLOWED-DOMAIN-IN-CSP"), + Finding( + ooi=http_header_hostname.reference, finding_type=KATFindingType(id="KAT-DISALLOWED-DOMAIN-IN-CSP").reference + ), + ] diff --git a/rocky/assets/js/jsonSchemaToForm.js b/rocky/assets/js/jsonSchemaToForm.js index 9d8fd668434..2803e9f8f92 100644 --- a/rocky/assets/js/jsonSchemaToForm.js +++ b/rocky/assets/js/jsonSchemaToForm.js @@ -6,6 +6,7 @@ window.addEventListener('load', (event) => { var inputtypes = { 'string': 'text', 'integer': 'number', + 'boolean': 'checkbox', } var formattypes = { @@ -212,9 +213,11 @@ function renderfield(required, originalvalue, path, name, field) { }); } else { if (!fieldformat) { - fieldformat = ['input', + fieldformat = [ + 'input', (field['type']?field['type']:'text'), - null]; + null + ]; } input = document.createElement(fieldformat[0]); input.type = fieldformat[1]; @@ -227,13 +230,12 @@ function renderfield(required, originalvalue, path, name, field) { if (field['pattern']) { input.pattern = field['pattern']; } - if (!field['format'] && - field['type']) { + if (!field['format'] && field['type']) { input.type = (field['type'] in inputtypes ? inputtypes[field['type']]: field['type']); } - if (field['type'] === 'number'){ + if (field['type'] == 'number'){ if (field['multipleOf']){ input.step = field['multipleOf']; } else { @@ -252,6 +254,9 @@ function renderfield(required, originalvalue, path, name, field) { input.max = field['exclusiveMaximum']-1; } } + if (field['type'] == 'boolean' && field['default']){ + input.checked = field['default']; + } if (field['default']) { input.value = field['default']; input.placeholder = field['default']; @@ -316,15 +321,18 @@ function ContentFromPostObject(wrapper, identifier, path, schema){ subpath = path + '_' + fieldname; childschema = schema['properties'][fieldname]; data = null; - if (schema['properties'][fieldname]['type'] == 'array'){ + let fieldtype = schema['properties'][fieldname]['type']; + if (fieldtype == 'array'){ data = ContentFromPostArray(wrapper, identifier, subpath, fieldname, childschema); - } else if (schema['properties'][fieldname]['type'] == 'object'){ + } else if (fieldtype == 'object'){ data = ContentFromPostObject(wrapper, identifier, subpath, childschema); + } else if (fieldtype == 'boolean'){ + data = wrapper.elements[identifier+subpath].checked; } else if (wrapper.elements[identifier+subpath]){ data = wrapper.elements[identifier+subpath].value; } - if (data){ + if (data || fieldtype == 'boolean'){ if (schema['properties'][fieldname]['type'] == 'number'){ data = parseFloat(data); } else if (schema['properties'][fieldname]['type'] == 'integer'){