def test_empty_specifier(self, version): spec = SpecifierSet(prereleases=True) assert version in spec assert spec.contains(version) assert parse(version) in spec assert spec.contains(parse(version))
def test_specifier_contains_prereleases(self): spec = SpecifierSet() assert spec.prereleases is None assert not spec.contains("1.0.dev1") assert spec.contains("1.0.dev1", prereleases=True) spec = SpecifierSet(prereleases=True) assert spec.prereleases assert spec.contains("1.0.dev1") assert not spec.contains("1.0.dev1", prereleases=False)
def test_specifier_contains_prereleases(self): spec = SpecifierSet() assert spec.prereleases is None assert not spec.contains("1.0.dev1") assert spec.contains("1.0.dev1", prereleases=True) spec = SpecifierSet(prereleases=True) assert spec.prereleases assert spec.contains("1.0.dev1") assert not spec.contains("1.0.dev1", prereleases=False)
def check_if_package_is_vulnerable( self, dependency: Dependency, ) -> Optional[Vulnerability]: """Check if the specified dependency has a vulnerability reported.""" if not self.data: raise VerificationError( "Cannot check vulnerability status, error when downloading database" ) database_entry = self.data.get(dependency.name) if database_entry is None: return None for vulnerability_entry in database_entry: constraint = vulnerability_entry["v"] specifier_set = SpecifierSet(constraint) if specifier_set.contains(dependency.version): return Vulnerability( advisory=vulnerability_entry["advisory"], cve=vulnerability_entry.get("cve"), versions_impacted=constraint, ) return None
def check(): db = fetch_database() db_full = None packages = frozenset(db.keys()) vulnerable = [] for pkg in pip.get_installed_distributions(): # normalize the package name, the safety-db is converting underscores to dashes and uses # lowercase name = pkg.key.replace("_", "-").lower() if name in packages: # we have a candidate here, build the spec set for specifier in db[name]: spec_set = SpecifierSet(specifiers=specifier) if spec_set.contains(pkg.version): if not db_full: db_full = fetch_database(full=True) for data in get_vulnerabilities(pkg=name, spec=specifier, db=db_full): vulnerable.append( Vulnerability(name=name, spec=specifier, version=pkg.version, data=data)) return vulnerable
def check(packages, key, db_mirror, cached, ignore_ids): key = key if key else os.environ.get("SAFETY_API_KEY", False) db = fetch_database(key=key, db=db_mirror, cached=cached) db_full = None vulnerable_packages = frozenset(db.keys()) vulnerable = [] for pkg in packages: # normalize the package name, the safety-db is converting underscores to dashes and uses # lowercase name = pkg.key.replace("_", "-").lower() if name in vulnerable_packages: # we have a candidate here, build the spec set for specifier in db[name]: spec_set = SpecifierSet(specifiers=specifier) if spec_set.contains(pkg.version): if not db_full: db_full = fetch_database(full=True, key=key, db=db_mirror) for data in get_vulnerabilities(pkg=name, spec=specifier, db=db_full): vuln_id = data.get("id").replace("pyup.io-", "") if vuln_id and vuln_id not in ignore_ids: vulnerable.append( Vulnerability( name=name, spec=specifier, version=pkg.version, advisory=data.get("advisory"), vuln_id=vuln_id ) ) return vulnerable
def check(packages, key, db_mirror, cached): db = fetch_database(key=key, db=db_mirror, cached=cached) db_full = None vulnerable_packages = frozenset(db.keys()) vulnerable = [] found_ids = set() for pkg in packages: # normalize the package name, the safety-db is converting underscores to dashes and uses # lowercase name = pkg.key.replace("_", "-").lower() if name in vulnerable_packages: # we have a candidate here, build the spec set for specifier in db[name]: spec_set = SpecifierSet(specifiers=specifier) if spec_set.contains(pkg.version): if not db_full: db_full = fetch_database(full=True, key=key, db=db_mirror) for data in get_vulnerabilities(pkg=name, spec=specifier, db=db_full): if data.get("id") not in found_ids: vulnerable.append( Vulnerability(name=name, spec=specifier, version=pkg.version, advisory=data.get("advisory"), vuln_id=data.get("id"))) found_ids.add(data.get("id")) return vulnerable
def check(packages, key, db_mirror, cached, ignore_ids, proxy): key = key if key else os.environ.get("SAFETY_API_KEY", False) db = fetch_database(key=key, db=db_mirror, cached=cached, proxy=proxy) db_full = None vulnerable_packages = frozenset(db.keys()) vulnerable = [] for pkg in packages: # normalize the package name, the safety-db is converting underscores to dashes and uses # lowercase name = pkg.key.replace("_", "-").lower() if name in vulnerable_packages: # we have a candidate here, build the spec set for specifier in db[name]: spec_set = SpecifierSet(specifiers=specifier) if spec_set.contains(pkg.version): if not db_full: db_full = fetch_database(full=True, key=key, db=db_mirror, cached=cached, proxy=proxy) for data in get_vulnerabilities(pkg=name, spec=specifier, db=db_full): vuln_id = data.get("id").replace("pyup.io-", "") if vuln_id and vuln_id not in ignore_ids: vulnerable.append( Vulnerability(name=name, spec=specifier, version=pkg.version, advisory=data.get("advisory"), vuln_id=vuln_id)) return vulnerable
def clean_requires_python(candidates): """Get a cleaned list of all the candidates with valid specifiers in the `requires_python` attributes.""" all_candidates = [] sys_version = ".".join(map(str, sys.version_info[:3])) from packaging.version import parse as parse_version py_version = parse_version( os.environ.get("PIP_PYTHON_VERSION", sys_version)) for c in candidates: from_location = attrgetter("location.requires_python") requires_python = getattr(c, "requires_python", from_location(c)) if requires_python: # Old specifications had people setting this to single digits # which is effectively the same as '>=digit,<digit+1' if requires_python.isdigit(): requires_python = ">={0},<{1}".format(requires_python, int(requires_python) + 1) try: specifierset = SpecifierSet(requires_python) except InvalidSpecifier: continue else: if not specifierset.contains(py_version): continue all_candidates.append(c) return all_candidates
def is_insecure(self, version): r = requests.get(settings.API_URL) if r.status_code == 200: for spec_str in r.json()['insecure']: spec = SpecifierSet(spec_str) if spec.contains(version): return True return False
def test_specifier_prereleases_explicit(self): spec = SpecifierSet() assert not spec.prereleases assert "1.0.dev1" not in spec assert not spec.contains("1.0.dev1") spec.prereleases = True assert spec.prereleases assert "1.0.dev1" in spec assert spec.contains("1.0.dev1") spec = SpecifierSet(prereleases=True) assert spec.prereleases assert "1.0.dev1" in spec assert spec.contains("1.0.dev1") spec.prereleases = False assert not spec.prereleases assert "1.0.dev1" not in spec assert not spec.contains("1.0.dev1") spec = SpecifierSet(prereleases=True) assert spec.prereleases assert "1.0.dev1" in spec assert spec.contains("1.0.dev1") spec.prereleases = None assert not spec.prereleases assert "1.0.dev1" not in spec assert not spec.contains("1.0.dev1")
def is_satisfied_by(self, installed_package): if not self.name == installed_package.name: return False # Any version matches if not self.has_version(): return True specifier_str = '%s%s' % (self.operator, self.version) specifier = SpecifierSet(specifier_str) version = Version(installed_package.version) return specifier.contains(version)
def get_latest_version_within_specs(specs, versions, prereleases=None): # build up a spec set and convert compatible specs to pinned ones spec_set = SpecifierSet( ",".join(["".join(s._spec).replace("~=", "==") for s in specs]) ) candidates = [] for version in versions: if spec_set.contains(version, prereleases=prereleases): candidates.append(version) candidates = sorted(candidates, key=lambda v: parse_version(v), reverse=True) if len(candidates) > 0: return candidates[0] return None
def _check_nox_version_satisfies(needs_version: str) -> None: """Check if the Nox version satisfies the given specifiers.""" version = Version(get_nox_version()) try: specifiers = SpecifierSet(needs_version) except InvalidSpecifier as error: message = f"Cannot parse `nox.needs_version`: {error}" with contextlib.suppress(InvalidVersion): Version(needs_version) message += f", did you mean '>= {needs_version}'?" raise InvalidVersionSpecifier(message) if not specifiers.contains(version, prereleases=True): raise VersionCheckFailed( f"The Noxfile requires Nox {specifiers}, you have {version}")
def process_requirements(requirements, version=None): """ Examples -------- >>> process_requirements(None) [] >>> process_requirements({"== 0.1": ["foo"]}, "0.1") ['foo'] >>> process_requirements({"< 0.2": ["foo"]}, "0.1") ['foo'] >>> process_requirements({"> 0.1, != 0.2": ["foo"]}, "0.3") ['foo'] >>> process_requirements({"== 0.1": ["foo"], "== 0.2": ["bar"]}, "0.2") ['bar'] >>> process_requirements({">= 0.1": ["foo"], ">= 0.2": ["bar"]}, "0.2") ['bar', 'foo'] >>> process_requirements({"== dev": ["foo"]}, "0.1") [] >>> process_requirements({"< dev": ["foo"]}, "0.1") ['foo'] >>> process_requirements({"> 0.1": ["foo"]}, "dev") ['foo'] >>> process_requirements({"== dev": ["foo"]}, "dev") ['foo'] >>> process_requirements({"> 0.1, != dev": ["foo"]}, "dev") [] """ if requirements is None: return [] if isinstance(requirements, list): return requirements if isinstance(requirements, dict): reqs = set() for specifier, packages in requirements.items(): specifier_set = SpecifierSet( specifier.replace(DEV_VERSION, DEV_NUMERIC)) if specifier_set.contains(DEV_NUMERIC if version == DEV_VERSION else version): reqs = reqs.union(packages) return sorted(reqs) raise TypeError("Invalid object type for `requirements`: '{}'".format( type(requirements)))
def check(packages, key, db_mirror, cached, ignore_ids, proxy): key = key if key else os.environ.get("SAFETY_API_KEY", False) db = fetch_database(key=key, db=db_mirror, cached=cached, proxy=proxy) db_full = None vulnerable_packages = frozenset(db.keys()) vulnerable = [] for pkg in packages: # Ignore recursive files not resolved if isinstance(pkg, RequirementFile): continue # normalize the package name, the safety-db is converting underscores to dashes and uses # lowercase name = pkg.key.replace("_", "-").lower() if name in vulnerable_packages: # we have a candidate here, build the spec set for specifier in db[name]: spec_set = SpecifierSet(specifiers=specifier) if spec_set.contains(pkg.version): if not db_full: db_full = fetch_database(full=True, key=key, db=db_mirror, cached=cached, proxy=proxy) for data in get_vulnerabilities(pkg=name, spec=specifier, db=db_full): vuln_id = data.get("id").replace("pyup.io-", "") cve_id = data.get("cve") if cve_id: cve_id = cve_id.split(",")[0].strip() if vuln_id and vuln_id not in ignore_ids: cve_meta = db_full.get("$meta", {}).get("cve", {}).get(cve_id, {}) vulnerable.append( Vulnerability( name=name, spec=specifier, version=pkg.version, advisory=data.get("advisory"), vuln_id=vuln_id, cvssv2=cve_meta.get("cvssv2", None), cvssv3=cve_meta.get("cvssv3", None), ) ) return vulnerable
def get_all_candidates(requirement): session = HTMLSession() url = f"https://pypi.org/simple/{requirement.key}" resp = session.get(url) for a in resp.html.find('a'): link = a.attrs['href'] python_requires = a.attrs.get('data-requires-python') filename = a.text if python_requires: spec = SpecifierSet(python_requires) if not spec.contains(PYTHON_VERSION): # Discard candidates that don't match the Python version. continue if not filename.endswith(".whl"): # Only parse wheels for this demo continue name, version = filename.split("-")[:2] if requirement.specifier.contains(version): yield Candidate(name, version, link)
def _match_node_at_path(self, key: str, metadata): # Grab any tags prepended to key tags = key.split(":") # Take anything following the last semicolon as the path to the node path = tags.pop() # Set our default matching rules for each key nulls_match = self.nulls_match # Interpret matching rules in tags if tags: for tag in tags: if tag == "not-null": nulls_match = False if tag == "match-null": nulls_match = True # Get value (List) of node using dotted path given by key node = self._find_element_by_dotted_path(path, metadata) # Check for null matching if nulls_match and not node: return True # Check if SpeciferSet matches target versions # TODO: Figure out proper intersection of SpecifierSets ospecs = SpecifierSet(node) ispecs = self.specifiers[key] if any(ospecs.contains(ispec, prereleases=True) for ispec in ispecs): return True # Otherwise, fail logger.info( f"Failed check for {key}='{ospecs}' against '{ispecs}'" # noqa: E501 ) return False
def test_legacy_specifiers_combined(self): spec = SpecifierSet("<3,>1-1-1") assert spec.contains("2.0")
def test_legacy_specifiers_combined(self): spec = SpecifierSet("<3,>1-1-1") assert spec.contains("2.0")
def test_empty_specifier(self, version): spec = SpecifierSet(prereleases=True) assert spec.contains(version) assert spec.contains(parse(version))
def check(packages, key=False, db_mirror=False, cached=0, ignore_vulns=None, ignore_severity_rules=None, proxy=None, include_ignored=False, is_env_scan=True, telemetry=True, params=None): SafetyContext().command = 'check' db = fetch_database(key=key, db=db_mirror, cached=cached, proxy=proxy, telemetry=telemetry) db_full = None vulnerable_packages = frozenset(db.keys()) vulnerabilities = [] for pkg in packages: # Ignore recursive files not resolved if isinstance(pkg, RequirementFile): continue # normalize the package name, the safety-db is converting underscores to dashes and uses # lowercase name = canonicalize_name(pkg.name) if name in vulnerable_packages: # we have a candidate here, build the spec set for specifier in db[name]: spec_set = SpecifierSet(specifiers=specifier) if spec_set.contains(pkg.version): if not db_full: db_full = fetch_database(full=True, key=key, db=db_mirror, cached=cached, proxy=proxy, telemetry=telemetry) for data in get_vulnerabilities(pkg=name, spec=specifier, db=db_full): vuln_id = data.get("id").replace("pyup.io-", "") cve = get_cve_from(data, db_full) ignore_vuln_if_needed(vuln_id, cve, ignore_vulns, ignore_severity_rules) vulnerability = get_vulnerability_from( vuln_id, cve, data, specifier, db, name, pkg, ignore_vulns) should_add_vuln = not (vulnerability.is_transitive and is_env_scan) if (include_ignored or vulnerability.vulnerability_id not in ignore_vulns) and should_add_vuln: vulnerabilities.append(vulnerability) return vulnerabilities, db_full
def version_matches(self, requirement): specifier_set = SpecifierSet(requirement.specifiers) return specifier_set.contains(self.package_version)