Hello community, here is the log from the commit of package python-pyhibp for openSUSE:Factory checked in at 2019-08-28 18:37:02 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pyhibp (Old) and /work/SRC/openSUSE:Factory/.python-pyhibp.new.7948 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pyhibp" Wed Aug 28 18:37:02 2019 rev:3 rq:726615 version:4.0.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pyhibp/python-pyhibp.changes 2019-07-02 10:39:47.186708781 +0200 +++ /work/SRC/openSUSE:Factory/.python-pyhibp.new.7948/python-pyhibp.changes 2019-08-28 18:37:07.869266500 +0200 @@ -1,0 +2,12 @@ +Tue Aug 27 14:45:31 UTC 2019 - Marketa Calabkova <[email protected]> + +- update to version 4.0.0 + * Breaking API change: The HIBP API now requires an API key for + calls which search by account. + * A User Agent must now be manually set. + * Python 2.7 Support Dropped + * Function modified: suffix_search(hash_prefix=prefix) is now the + method to search for hash suffixes. + * Read upstream changelog for more info. + +------------------------------------------------------------------- Old: ---- pyhibp-3.1.0.tar.gz New: ---- pyhibp-4.0.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pyhibp.spec ++++++ --- /var/tmp/diff_new_pack.plwHdw/_old 2019-08-28 18:37:08.737266349 +0200 +++ /var/tmp/diff_new_pack.plwHdw/_new 2019-08-28 18:37:08.737266349 +0200 @@ -17,22 +17,20 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} +%define skip_python2 1 Name: python-pyhibp -Version: 3.1.0 +Version: 4.0.0 Release: 0 Summary: An interface to Troy Hunt's 'Have I Been Pwned' public API License: AGPL-3.0-or-later Group: Development/Languages/Python URL: https://gitlab.com/kitsunix/pyHIBP/pyHIBP Source: https://files.pythonhosted.org/packages/source/p/pyhibp/pyhibp-%{version}.tar.gz -BuildRequires: %{python_module pytest} BuildRequires: %{python_module requests >= 2.20.0 } BuildRequires: %{python_module setuptools} -BuildRequires: %{python_module tox} BuildRequires: fdupes BuildRequires: python-rpm-macros Requires: python-requests >= 2.20.0 -Requires: python-six >= 1.11.0 BuildArch: noarch %python_subpackages ++++++ pyhibp-3.1.0.tar.gz -> pyhibp-4.0.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/CHANGELOG.md new/pyhibp-4.0.0/CHANGELOG.md --- old/pyhibp-3.1.0/CHANGELOG.md 2019-06-30 10:46:01.000000000 +0200 +++ new/pyhibp-4.0.0/CHANGELOG.md 2019-08-11 06:27:23.000000000 +0200 @@ -1,6 +1,33 @@ pyHIBP Changelog ================ -v3.1.0 (In progress...) +v4.0.0 (2019-08-11) +------------------------ +- **Breaking API change**: The HIBP API now requires an API key for calls which search by account. This means calls to + ``pyhibp.get_account_breaches()`` and ``pyhibp.get_pastes()``. API keys can be obtained [the HIBP website](https://haveibeenpwned.com/API/Key), + and must be loaded into the module by calling ``pyhibp.set_api_key(key="your_key")`` prior to calling the affected + functions. +- The other functions inside ``pyhibp``, as well as ``pwnedpasswords``, do not require an API key to use. +- As a note, our testing harness has not been tested against valid API keys, however we anticipate no issues. If issues + are discovered, please raise an issue with details, ideally with a merge request to fix the issue. +- **A User Agent must now be manually set**: As per the HIBP API, the User-Agent header set in calls to the API must + reflect the name of the calling application. Directly from the API documentation, "the user agent should accurately + describe the nature of the API consumer such that it can be clearly identified in the request. Not doing so may result + in the request being blocked." As such, users of this module must now set the User-Agent by calling + ``pyhibp.set_user_agent(ua=ua_string)``, where ``ua_string`` is a string which identifies the application implementing + the ``pyhibp`` module. +- **Python 2.7 Support Dropped**: With Python 2.7 support being dropped in January 2020, as stated in the notes for v3.0.0 + and v3.1.0, ``pyhibp`` is dropping support for Python 2.7. +- **Function modified**: ``suffix_search(hash_prefix=prefix)`` is now the method to search for hash suffixes. The compatability + shim left when introducing the function in v3.1.0 has been removed (and thus ``first_5_hash_chars`` is no longer a valid + parameter to the function ``is_password_breached()``). +- **Return type changes**: As per the changelog from v3.1.0, the return type for empty sets has changed as follows for the + following functions in the ``pyhibp`` module: + - ``get_account_breaches`` -> ``[] / list`` + - ``get_all_breaches`` -> ``[] / list`` + - ``get_single_breach`` -> ``{} / dict`` + - ``get_pastes`` -> ``[] / list`` + +v3.1.0 (2019-06-30) ----------------------- - **New function**: ``pwnedpasswords.suffix_search(hash_prefix=prefix)`` was created in order to have a dedicated function return the suffix list. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/PKG-INFO new/pyhibp-4.0.0/PKG-INFO --- old/pyhibp-3.1.0/PKG-INFO 2019-06-30 11:17:16.000000000 +0200 +++ new/pyhibp-4.0.0/PKG-INFO 2019-08-11 06:27:37.000000000 +0200 @@ -1,102 +1,116 @@ -Metadata-Version: 2.1 -Name: pyhibp -Version: 3.1.0 -Summary: An interface to Troy Hunt's 'Have I Been Pwned' public API -Home-page: https://gitlab.com/kitsunix/pyHIBP/pyHIBP -Author: Kyra F. Kitsune -License: UNKNOWN -Description: pyHIBP (pyHave I Been Pwned) - ============================ - [](https://pypi.org/project/pyHIBP/) - [](https://pypi.org/project/pyHIBP/) - [](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master) - - - A Python interface to Troy Hunt's 'Have I Been Pwned?' (HIBP) public API. A full reference to the API - specification can be found at the [HIBP API Reference](https://haveibeenpwned.com/API/v2). - - This module detects when the rate limit of the API has been hit, and raises a RuntimeError when the limit - is exceeded, or when another API-defined error condition is encountered based on the submitted data. When - data is found from a call, the data returned will be in the format as retrieved from the endpoint, documented - in the return-type information for the relevant function. - - Note that the `pwnedpasswords` API backend does not have a rate limit. If you are intending to bulk-query passwords or - hashes, you should consider downloading the raw data files accessible via the [Pwned Passwords](https://haveibeenpwned.com/Passwords) page. - - Installing - ---------- - ```bash - $ pip install pyhibp - ``` - - Example usage - ------------- - For an interactive example, check out the Jupyter Notebook for [`pyhibp`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.ipynb), - as well as [`pyhibp.pwnedpasswords`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.pwnedpasswords.ipynb). - - ```python - import pyhibp - from pyhibp import pwnedpasswords as pw - - # Check a password to see if it has been disclosed in a public breach corpus - resp = pw.is_password_breached(password="secret") - if resp: - print("Password breached!") - print("This password was used {0} time(s) before.".format(resp)) - - # Get breaches that affect a given account - resp = pyhibp.get_account_breaches(account="[email protected]", truncate_response=True) - - # Get all breach information - resp = pyhibp.get_all_breaches() - - # Get a single breach - resp = pyhibp.get_single_breach(breach_name="Adobe") - - # Get pastes affecting a given email address - resp = pyhibp.get_pastes(email_address="[email protected]") - - # Get data classes in the HIBP system - resp = pyhibp.get_data_classes() - ``` - - Developing - ---------- - This project is currently intended to be compatible with Python 2 and Python 3. As such, we use virtual environments via `pipenv`. - To develop or test, execute the following: - - ```bash - # Install the prerequisite virtual environment provider - $ pip install pipenv - # Initialize the pipenv environment and install the module within it - $ make dev - # To run PEP8, tests, and check the manifest - $ make tox - ``` - - Other commands can be found in the `Makefile`. - - Goals - ----- - - Synchronize to the latest HIBP API(s), implementing endpoint accessing functions where it makes sense. For instance, - in the interest of security, the ability to submit a SHA-1 to the Pwned Passwords endpoint is not implemented. See - "Regarding password checking" below for further details. - - For breaches and pastes, act as an intermediary; return the JSON as received from the service. - - Regarding password checking - --------------------------- - - For passwords, the option to supply a plaintext password to check is provided as an implementation convenience. - - For added security, `pwnedpasswords.is_password_breached()` only transmits the first five characters of the SHA-1 - hash to the Pwned Passwords API endpoint; a secure password will remain secure without disclosing the full hash. - -Platform: UNKNOWN -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+) -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 -Description-Content-Type: text/markdown -Provides-Extra: dev +Metadata-Version: 2.1 +Name: pyhibp +Version: 4.0.0 +Summary: An interface to Troy Hunt's 'Have I Been Pwned' public API +Home-page: https://gitlab.com/kitsunix/pyHIBP/pyHIBP +Author: Kyra F. Kitsune +License: UNKNOWN +Description: pyHIBP (pyHave I Been Pwned) + ============================ + [](https://pypi.org/project/pyHIBP/) + [](https://pypi.org/project/pyHIBP/) + [](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master) + + + A Python interface to Troy Hunt's 'Have I Been Pwned?' (HIBP) public API. A full reference to the API + specification can be found at the [HIBP API Reference](https://haveibeenpwned.com/API/v2). + + This module detects when the rate limit of the API has been hit, and raises a RuntimeError when the limit + is exceeded, or when another API-defined error condition is encountered based on the submitted data. When + data is found from a call, the data returned will be in the format as retrieved from the endpoint, documented + in the return-type information for the relevant function. + + Note that the `pwnedpasswords` API backend does not have a rate limit. If you are intending to bulk-query passwords or + hashes, you should consider downloading the raw data files accessible via the [Pwned Passwords](https://haveibeenpwned.com/Passwords) page. + + Installing + ---------- + ```bash + $ pip install pyhibp + ``` + + Example usage + ------------- + For an interactive example, check out the Jupyter Notebook for [`pyhibp`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.ipynb), + as well as [`pyhibp.pwnedpasswords`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.pwnedpasswords.ipynb). + + ```python + import pyhibp + from pyhibp import pwnedpasswords as pw + + # Required: A descriptive user agent must be set describing the application consuming + # the HIBP API + pyhibp.set_user_agent(ua="Awesome application/0.0.1 (An awesome description)") + + # Check a password to see if it has been disclosed in a public breach corpus + resp = pw.is_password_breached(password="secret") + if resp: + print("Password breached!") + print("This password was used {0} time(s) before.".format(resp)) + + # Get data classes in the HIBP system + resp = pyhibp.get_data_classes() + + # Get all breach information + resp = pyhibp.get_all_breaches() + + # Get a single breach + resp = pyhibp.get_single_breach(breach_name="Adobe") + + # An API key is required for calls which search by email address + # (so get_pastes/get_account_breaches) + # See <https://haveibeenpwned.com/API/Key> + HIBP_API_KEY = None + + if HIBP_API_KEY: + # Set the API key prior to using the functions which require it. + pyhibp.set_api_key(key=HIBP_API_KEY) + + # Get pastes affecting a given email address + resp = pyhibp.get_pastes(email_address="[email protected]") + + # Get breaches that affect a given account + resp = pyhibp.get_account_breaches(account="[email protected]", truncate_response=True) + ``` + + Developing + ---------- + This project is currently intended to be compatible with Python 2 and Python 3. As such, we use virtual environments via `pipenv`. + To develop or test, execute the following: + + ```bash + # Install the prerequisite virtual environment provider + $ pip install pipenv + # Initialize the pipenv environment and install the module within it + $ make dev + # To run PEP8, tests, and check the manifest + $ make tox + ``` + + Other commands can be found in the `Makefile`. + + Goals + ----- + - Synchronize to the latest HIBP API(s), implementing endpoint accessing functions where it makes sense. For instance, + in the interest of security, the ability to submit a SHA-1 to the Pwned Passwords endpoint is not implemented. See + "Regarding password checking" below for further details. + - For breaches and pastes, act as an intermediary; return the JSON as received from the service. + + Regarding password checking + --------------------------- + - For passwords, the option to supply a plaintext password to check is provided as an implementation convenience. + - For added security, `pwnedpasswords.is_password_breached()` only transmits the first five characters of the SHA-1 + hash to the Pwned Passwords API endpoint; a secure password will remain secure without disclosing the full hash. + +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+) +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Description-Content-Type: text/markdown +Provides-Extra: dev diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/Pipfile new/pyhibp-4.0.0/Pipfile --- old/pyhibp-3.1.0/Pipfile 2019-06-30 10:46:01.000000000 +0200 +++ new/pyhibp-4.0.0/Pipfile 2019-08-11 06:27:23.000000000 +0200 @@ -5,7 +5,6 @@ [packages] requests = ">=2.20.0" -six = ">=1.11.0" [dev-packages] tox = "*" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/README.md new/pyhibp-4.0.0/README.md --- old/pyhibp-3.1.0/README.md 2019-06-30 10:46:01.000000000 +0200 +++ new/pyhibp-4.0.0/README.md 2019-08-11 06:27:23.000000000 +0200 @@ -31,14 +31,18 @@ import pyhibp from pyhibp import pwnedpasswords as pw +# Required: A descriptive user agent must be set describing the application consuming +# the HIBP API +pyhibp.set_user_agent(ua="Awesome application/0.0.1 (An awesome description)") + # Check a password to see if it has been disclosed in a public breach corpus resp = pw.is_password_breached(password="secret") if resp: print("Password breached!") print("This password was used {0} time(s) before.".format(resp)) -# Get breaches that affect a given account -resp = pyhibp.get_account_breaches(account="[email protected]", truncate_response=True) +# Get data classes in the HIBP system +resp = pyhibp.get_data_classes() # Get all breach information resp = pyhibp.get_all_breaches() @@ -46,11 +50,20 @@ # Get a single breach resp = pyhibp.get_single_breach(breach_name="Adobe") -# Get pastes affecting a given email address -resp = pyhibp.get_pastes(email_address="[email protected]") +# An API key is required for calls which search by email address +# (so get_pastes/get_account_breaches) +# See <https://haveibeenpwned.com/API/Key> +HIBP_API_KEY = None + +if HIBP_API_KEY: + # Set the API key prior to using the functions which require it. + pyhibp.set_api_key(key=HIBP_API_KEY) -# Get data classes in the HIBP system -resp = pyhibp.get_data_classes() + # Get pastes affecting a given email address + resp = pyhibp.get_pastes(email_address="[email protected]") + + # Get breaches that affect a given account + resp = pyhibp.get_account_breaches(account="[email protected]", truncate_response=True) ``` Developing diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/setup.cfg new/pyhibp-4.0.0/setup.cfg --- old/pyhibp-3.1.0/setup.cfg 2019-06-30 11:17:16.000000000 +0200 +++ new/pyhibp-4.0.0/setup.cfg 2019-08-11 06:27:37.000000000 +0200 @@ -1,11 +1,11 @@ -[bdist_wheel] -universal = 1 - -[flake8] -exclude = .git -ignore = E501 - -[egg_info] -tag_build = -tag_date = 0 - +[bdist_wheel] +universal = 0 + +[flake8] +exclude = .git +ignore = E501 + +[egg_info] +tag_build = +tag_date = 0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/setup.py new/pyhibp-4.0.0/setup.py --- old/pyhibp-3.1.0/setup.py 2019-06-30 10:44:52.000000000 +0200 +++ new/pyhibp-4.0.0/setup.py 2019-08-11 06:27:23.000000000 +0200 @@ -107,10 +107,11 @@ # Specify the Python versions you support here. In particular, ensure # that you indicate whether you support Python 2, Python 3 or both. - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', ], # This field adds keywords for your project which will appear on the @@ -141,7 +142,6 @@ # https://packaging.python.org/en/latest/requirements.html install_requires=[ "requests >= 2.20.0", - "six >= 1.11.0", ], # Optional # List additional groups of dependencies here (e.g. development diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/src/pyhibp/__init__.py new/pyhibp-4.0.0/src/pyhibp/__init__.py --- old/pyhibp-3.1.0/src/pyhibp/__init__.py 2019-06-30 10:46:01.000000000 +0200 +++ new/pyhibp-4.0.0/src/pyhibp/__init__.py 2019-08-11 06:27:23.000000000 +0200 @@ -1,22 +1,81 @@ import requests -import six -from pyhibp import __version__ -HIBP_API_BASE_URI = "https://haveibeenpwned.com/api/v2/" +HIBP_API_BASE_URI = "https://haveibeenpwned.com/api/v3/" HIBP_API_ENDPOINT_BREACH_SINGLE = "breach/" HIBP_API_ENDPOINT_BREACHES = "breaches" HIBP_API_ENDPOINT_BREACHED_ACCT = "breachedaccount/" HIBP_API_ENDPOINT_DATA_CLASSES = "dataclasses" HIBP_API_ENDPOINT_PASTES = "pasteaccount/" -# The HIBP API requires that a useragent be set. -pyHIBP_USERAGENT = "pyHIBP/{0} (A Python interface to the public HIBP API)".format(__version__.__version__) + # The headers we send along with each request -pyHIBP_HEADERS = {'User-Agent': pyHIBP_USERAGENT} +pyHIBP_HEADERS = { + 'User-Agent': None, + 'hibp-api-key': None +} + + +def _require_user_agent(function): + """ + A decorator which enforces setting the User-Agent on each request. As per the HIBP API, the UA must be set, or a + HTTP 403 response wil; be raised. + + :raises: RuntimeError if the application User-Agent is not set via ``set_user_agent()`` prior to invoking functions + which communicate with the HIBP or PwnedPasswords APIs. + """ + def inner(*args, **kwargs): + if pyHIBP_HEADERS.get("User-Agent") is None: + raise RuntimeError("The User-Agent must be set. Call pyhibp.set_user_agent(ua=your_agent_string) first.") + return function(*args, **kwargs) + return inner + + +def set_user_agent(ua: str = None): + """ + Sets the User-Agent to be used in subsequent calls sent to the HIBP API backend. The UA set should be the name of the + application implementing the pyhibp module, and accurately describe the nature of the API consumer. + + See: https://haveibeenpwned.com/API/v3#UserAgent + + The HIBP API enforces setting a User-Agent, otherwise an HTTP 403 will be returned. + + :param ua: A string representing the application name which is implementing the pyhibp module which is sent with + each request to the HIBP API. + """ + pyHIBP_HEADERS['User-Agent'] = ua + + +def _require_api_key(function): + """ + A decorator which enforces setting an API key on functions which require one to be set. Not setting an API key would + result in an HTTP 401 Unauthorised. + + :raises: RuntimeError if the API key for enabling searching the HIBP API backend by account (email) is not set prior + to calling functions which invoke said capability. + """ + def inner(*args, **kwargs): + if pyHIBP_HEADERS.get('hibp-api-key') is None: + raise RuntimeError("A HIBP API key is required for this call. Call pyhibp.set_api_key(key=your_key) first.") + return function(*args, **kwargs) + return inner + +def set_api_key(key: str = None): + """ + Set an API key for use in all future calls to the pyhibp module which search the HIBP database by email address. + + Per the HIBP API documentation: "Authorisation is required for all APIs that enable searching HIBP by email address, + namely retrieving all breaches for an account and retrieving all pastes for an account." + + A key can be purchased from <https://haveibeenpwned.com/API/Key>. + + :param key: The key to set for each subsequent request where it is required. + """ + pyHIBP_HEADERS['hibp-api-key'] = key -def _process_response(response): + +def _process_response(response) -> bool: """ Process the `requests` response from the call to the HIBP API endpoints. @@ -35,6 +94,11 @@ # Bad request - The account does not comply with an acceptable format (i.e., it's an empty string) raise RuntimeError( "HTTP 400 - Bad request - The account does not comply with an acceptable format (i.e., it's an empty string).") + elif response.status_code == 401: + # Unauthorised - the API key provided was not valid + raise RuntimeError( + "HTTP 401 - Unauthorised - The API key provided was not valid." + ) elif response.status_code == 403: # Forbidden - no user agent has been specified in the request raise RuntimeError("HTTP 403 - User agent required for HIBP API requests, but no user agent was sent to the API endpoint.") @@ -48,13 +112,17 @@ raise NotImplementedError("Returned HTTP status code of {0} was not expected.".format(response.status_code)) -def get_account_breaches(account=None, domain=None, truncate_response=False, include_unverified=False): +@_require_api_key +@_require_user_agent +def get_account_breaches(account: str = None, domain: str = None, truncate_response: bool = False, include_unverified: bool = False) -> list: """ Gets breaches for a specified account from the HIBP system, optionally restricting the returned results to a specified domain. - :param account: The user's account name (such as an email address or a user-name). Default None. `str` type. - :param domain: The domain to check for breaches. Default None. `str` type. + This function requires a HIBP API key to be set. See ``set_api_key()``. + + :param account: The user's account name (such as an email address or a user-name). Default None. `str` type. Required. + :param domain: The domain to check for breaches. Default None. `str` type. Optional :param truncate_response: If ``account`` is specified, truncates the response down to the breach names. Default False. `bool` type. :param include_unverified: If set to True, unverified breaches are included in the result. Default False. `bool` type @@ -63,10 +131,10 @@ the HIBP API. :rtype: list """ - # Account/Domain don't need to be specified, but they must be text if so. - if account is None or not isinstance(account, six.string_types): + if account is None or not isinstance(account, str): raise AttributeError("The account parameter must be specified, and must be a string.") - if domain is not None and not isinstance(domain, six.string_types): + # The domain does not need to specified, but is must be text, if so. + if domain is not None and not isinstance(domain, str): raise AttributeError("The domain parameter, if specified, must be a string.") uri = HIBP_API_BASE_URI + HIBP_API_ENDPOINT_BREACHED_ACCT + account @@ -79,14 +147,15 @@ "includeUnverified": include_unverified, } resp = requests.get(url=uri, params=query_string_payload, headers=pyHIBP_HEADERS) + if _process_response(response=resp): return resp.json() else: - # TODO: v4.0.0: return [] - return False + return [] -def get_all_breaches(domain=None): +@_require_user_agent +def get_all_breaches(domain: str = None) -> list: """ Returns a listing of all sites breached in the HIBP database. @@ -95,21 +164,22 @@ if ``domain`` is specified, but the resultant list would be length zero. :rtype: list """ - if domain is not None and not isinstance(domain, six.string_types): + if domain is not None and not isinstance(domain, str): raise AttributeError("The domain parameter, if specified, must be a string.") uri = HIBP_API_BASE_URI + HIBP_API_ENDPOINT_BREACHES query_string_payload = {'domain': domain} resp = requests.get(url=uri, params=query_string_payload, headers=pyHIBP_HEADERS) + # The API will return HTTP200 even if resp.json is length zero. if _process_response(response=resp) and len(resp.json()) > 0: return resp.json() else: - # TODO: v4.0.0: return [] - return False + return [] -def get_single_breach(breach_name=None): +@_require_user_agent +def get_single_breach(breach_name: str = None) -> dict: """ Returns a single breach's information from the HIBP's database. @@ -118,40 +188,45 @@ database. Boolean False is returned if the specified breach was not found. :rtype: dict """ - if not isinstance(breach_name, six.string_types): + if not isinstance(breach_name, str): raise AttributeError("The breach_name must be specified, and be a string.") uri = HIBP_API_BASE_URI + HIBP_API_ENDPOINT_BREACH_SINGLE + breach_name resp = requests.get(url=uri, headers=pyHIBP_HEADERS) + if _process_response(response=resp): return resp.json() else: - # TODO: v4.0.0: return {} - return False + return {} -def get_pastes(email_address=None): +@_require_api_key +@_require_user_agent +def get_pastes(email_address: str = None) -> list: """ Retrieve all pastes for a specified email address. + This function requires a HIBP API key to be set. See ``set_api_key()``. + :param email_address: The email address to search. Required. `str` type. :return: A list object containing one or more dict objects corresponding to the pastes the specified email address was found in. Boolean False returned if no pastes are detected for the given account. :rtype: list """ - if not isinstance(email_address, six.string_types): + if not isinstance(email_address, str): raise AttributeError("The email address supplied must be provided, and be a string.") uri = HIBP_API_BASE_URI + HIBP_API_ENDPOINT_PASTES + email_address resp = requests.get(url=uri, headers=pyHIBP_HEADERS) + if _process_response(response=resp): return resp.json() else: - # TODO: v4.0.0: return [] - return False + return [] -def get_data_classes(): +@_require_user_agent +def get_data_classes() -> list: """ Retrieves all available data classes from the HIBP API. @@ -161,6 +236,7 @@ """ uri = HIBP_API_BASE_URI + HIBP_API_ENDPOINT_DATA_CLASSES resp = requests.get(url=uri, headers=pyHIBP_HEADERS) + if _process_response(response=resp): return resp.json() else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/src/pyhibp/__version__.py new/pyhibp-4.0.0/src/pyhibp/__version__.py --- old/pyhibp-3.1.0/src/pyhibp/__version__.py 2019-06-30 11:09:11.000000000 +0200 +++ new/pyhibp-4.0.0/src/pyhibp/__version__.py 2019-08-11 06:27:23.000000000 +0200 @@ -4,5 +4,5 @@ # |)\/| |||)| # | / -__version__ = '3.1.0' +__version__ = '4.0.0' __url__ = 'https://gitlab.com/kitsunix/pyHIBP/pyHIBP' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/src/pyhibp/pwnedpasswords.py new/pyhibp-4.0.0/src/pyhibp/pwnedpasswords.py --- old/pyhibp-3.1.0/src/pyhibp/pwnedpasswords.py 2019-06-30 10:46:02.000000000 +0200 +++ new/pyhibp-4.0.0/src/pyhibp/pwnedpasswords.py 2019-08-11 06:27:23.000000000 +0200 @@ -1,10 +1,9 @@ import hashlib -import warnings import requests -import six import pyhibp +from pyhibp import _require_user_agent PWNED_PASSWORDS_API_BASE_URI = "https://api.pwnedpasswords.com/" PWNED_PASSWORDS_API_ENDPOINT_RANGE_SEARCH = "range/" @@ -12,7 +11,7 @@ RESPONSE_ENCODING = "utf-8-sig" -def is_password_breached(password=None, sha1_hash=None, first_5_hash_chars=None): +def is_password_breached(password: str = None, sha1_hash: str = None) -> int: """ Execute a search for a password via the k-anonymity model, checking for hashes which match a specified prefix instead of supplying the full hash to the Pwned Passwords API. @@ -39,46 +38,34 @@ :rtype: int """ # Parameter validation section - if not any([password, first_5_hash_chars, sha1_hash]): - raise AttributeError("One of password, sha1_hash, or first_5_hash_chars must be provided.") - elif password is not None and not isinstance(password, six.string_types): - raise AttributeError("password must be a string type.") - elif sha1_hash is not None and not isinstance(sha1_hash, six.string_types): - raise AttributeError("sha1_hash must be a string type.") - elif first_5_hash_chars is not None and not isinstance(first_5_hash_chars, six.string_types): - raise AttributeError("first_5_hash_chars must be a string type.") - if first_5_hash_chars and len(first_5_hash_chars) != 5: - raise AttributeError("first_5_hash_chars must be of length 5.") + if not any([password, sha1_hash]): + raise AttributeError("Either password or sha1_hash must be provided.") + elif password is not None and not isinstance(password, str): + raise AttributeError("password must be a string.") + elif sha1_hash is not None and not isinstance(sha1_hash, str): + raise AttributeError("sha1_hash must be a string.") if password: sha1_hash = hashlib.sha1(password.encode('utf-8')).hexdigest() if sha1_hash: # The HIBP API stores the SHA-1 hashes in uppercase, so ensure we have it as uppercase here sha1_hash = sha1_hash.upper() - first_5_hash_chars = sha1_hash[0:5] + hash_prefix = sha1_hash[0:5] - suffix_list = suffix_search(hash_prefix=first_5_hash_chars) + suffix_list = suffix_search(hash_prefix=hash_prefix) - if not sha1_hash: - # TODO: v4.0.0: Remove this codepath (first_5_hash_chars) - warnings.warn(""" - Hash suffix searching is being moved to its own discrete function, `suffix_search()`. Call `suffix_search(hash_prefix=prefix)` - instead. Hash prefixes will not return from is_password_breached() in a future release. - """) - # Return the list of hash suffixes. - return suffix_list - else: - # Since the full SHA-1 hash was provided, check to see if it was in the resultant hash suffixes returned. - for hash_suffix in suffix_list: - if sha1_hash[5:] in hash_suffix: - # We found the full hash, so return - return int(hash_suffix.split(':')[1]) + # Since the full SHA-1 hash was provided, check to see if it was in the resultant hash suffixes returned. + for hash_suffix in suffix_list: + if sha1_hash[5:] in hash_suffix: + # We found the full hash, so return + return int(hash_suffix.split(':')[1]) - # If we get here, there was no match to the supplied SHA-1 hash; return zero. - return 0 + # If we get here, there was no match to the supplied SHA-1 hash; return zero. + return 0 -def suffix_search(hash_prefix=None): +@_require_user_agent +def suffix_search(hash_prefix: str = None) -> list: """ Returns a list of SHA-1 hash suffixes, consisting of the SHA-1 hash characters after position five, and the number of times that password hash was found in the HIBP database, colon separated. @@ -105,8 +92,8 @@ :return: A list of hash suffixes. :rtype: list """ - if not hash_prefix or not isinstance(hash_prefix, six.string_types): - raise AttributeError("hash_prefix must be a supplied, and be a string-type.") + if not hash_prefix or not isinstance(hash_prefix, str): + raise AttributeError("hash_prefix must be a supplied, and be a string.") if hash_prefix and len(hash_prefix) != 5: raise AttributeError("hash_prefix must be of length 5.") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/src/pyhibp.egg-info/PKG-INFO new/pyhibp-4.0.0/src/pyhibp.egg-info/PKG-INFO --- old/pyhibp-3.1.0/src/pyhibp.egg-info/PKG-INFO 2019-06-30 11:17:15.000000000 +0200 +++ new/pyhibp-4.0.0/src/pyhibp.egg-info/PKG-INFO 2019-08-11 06:27:37.000000000 +0200 @@ -1,102 +1,116 @@ -Metadata-Version: 2.1 -Name: pyhibp -Version: 3.1.0 -Summary: An interface to Troy Hunt's 'Have I Been Pwned' public API -Home-page: https://gitlab.com/kitsunix/pyHIBP/pyHIBP -Author: Kyra F. Kitsune -License: UNKNOWN -Description: pyHIBP (pyHave I Been Pwned) - ============================ - [](https://pypi.org/project/pyHIBP/) - [](https://pypi.org/project/pyHIBP/) - [](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master) - - - A Python interface to Troy Hunt's 'Have I Been Pwned?' (HIBP) public API. A full reference to the API - specification can be found at the [HIBP API Reference](https://haveibeenpwned.com/API/v2). - - This module detects when the rate limit of the API has been hit, and raises a RuntimeError when the limit - is exceeded, or when another API-defined error condition is encountered based on the submitted data. When - data is found from a call, the data returned will be in the format as retrieved from the endpoint, documented - in the return-type information for the relevant function. - - Note that the `pwnedpasswords` API backend does not have a rate limit. If you are intending to bulk-query passwords or - hashes, you should consider downloading the raw data files accessible via the [Pwned Passwords](https://haveibeenpwned.com/Passwords) page. - - Installing - ---------- - ```bash - $ pip install pyhibp - ``` - - Example usage - ------------- - For an interactive example, check out the Jupyter Notebook for [`pyhibp`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.ipynb), - as well as [`pyhibp.pwnedpasswords`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.pwnedpasswords.ipynb). - - ```python - import pyhibp - from pyhibp import pwnedpasswords as pw - - # Check a password to see if it has been disclosed in a public breach corpus - resp = pw.is_password_breached(password="secret") - if resp: - print("Password breached!") - print("This password was used {0} time(s) before.".format(resp)) - - # Get breaches that affect a given account - resp = pyhibp.get_account_breaches(account="[email protected]", truncate_response=True) - - # Get all breach information - resp = pyhibp.get_all_breaches() - - # Get a single breach - resp = pyhibp.get_single_breach(breach_name="Adobe") - - # Get pastes affecting a given email address - resp = pyhibp.get_pastes(email_address="[email protected]") - - # Get data classes in the HIBP system - resp = pyhibp.get_data_classes() - ``` - - Developing - ---------- - This project is currently intended to be compatible with Python 2 and Python 3. As such, we use virtual environments via `pipenv`. - To develop or test, execute the following: - - ```bash - # Install the prerequisite virtual environment provider - $ pip install pipenv - # Initialize the pipenv environment and install the module within it - $ make dev - # To run PEP8, tests, and check the manifest - $ make tox - ``` - - Other commands can be found in the `Makefile`. - - Goals - ----- - - Synchronize to the latest HIBP API(s), implementing endpoint accessing functions where it makes sense. For instance, - in the interest of security, the ability to submit a SHA-1 to the Pwned Passwords endpoint is not implemented. See - "Regarding password checking" below for further details. - - For breaches and pastes, act as an intermediary; return the JSON as received from the service. - - Regarding password checking - --------------------------- - - For passwords, the option to supply a plaintext password to check is provided as an implementation convenience. - - For added security, `pwnedpasswords.is_password_breached()` only transmits the first five characters of the SHA-1 - hash to the Pwned Passwords API endpoint; a secure password will remain secure without disclosing the full hash. - -Platform: UNKNOWN -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+) -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 -Description-Content-Type: text/markdown -Provides-Extra: dev +Metadata-Version: 2.1 +Name: pyhibp +Version: 4.0.0 +Summary: An interface to Troy Hunt's 'Have I Been Pwned' public API +Home-page: https://gitlab.com/kitsunix/pyHIBP/pyHIBP +Author: Kyra F. Kitsune +License: UNKNOWN +Description: pyHIBP (pyHave I Been Pwned) + ============================ + [](https://pypi.org/project/pyHIBP/) + [](https://pypi.org/project/pyHIBP/) + [](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master) + + + A Python interface to Troy Hunt's 'Have I Been Pwned?' (HIBP) public API. A full reference to the API + specification can be found at the [HIBP API Reference](https://haveibeenpwned.com/API/v2). + + This module detects when the rate limit of the API has been hit, and raises a RuntimeError when the limit + is exceeded, or when another API-defined error condition is encountered based on the submitted data. When + data is found from a call, the data returned will be in the format as retrieved from the endpoint, documented + in the return-type information for the relevant function. + + Note that the `pwnedpasswords` API backend does not have a rate limit. If you are intending to bulk-query passwords or + hashes, you should consider downloading the raw data files accessible via the [Pwned Passwords](https://haveibeenpwned.com/Passwords) page. + + Installing + ---------- + ```bash + $ pip install pyhibp + ``` + + Example usage + ------------- + For an interactive example, check out the Jupyter Notebook for [`pyhibp`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.ipynb), + as well as [`pyhibp.pwnedpasswords`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.pwnedpasswords.ipynb). + + ```python + import pyhibp + from pyhibp import pwnedpasswords as pw + + # Required: A descriptive user agent must be set describing the application consuming + # the HIBP API + pyhibp.set_user_agent(ua="Awesome application/0.0.1 (An awesome description)") + + # Check a password to see if it has been disclosed in a public breach corpus + resp = pw.is_password_breached(password="secret") + if resp: + print("Password breached!") + print("This password was used {0} time(s) before.".format(resp)) + + # Get data classes in the HIBP system + resp = pyhibp.get_data_classes() + + # Get all breach information + resp = pyhibp.get_all_breaches() + + # Get a single breach + resp = pyhibp.get_single_breach(breach_name="Adobe") + + # An API key is required for calls which search by email address + # (so get_pastes/get_account_breaches) + # See <https://haveibeenpwned.com/API/Key> + HIBP_API_KEY = None + + if HIBP_API_KEY: + # Set the API key prior to using the functions which require it. + pyhibp.set_api_key(key=HIBP_API_KEY) + + # Get pastes affecting a given email address + resp = pyhibp.get_pastes(email_address="[email protected]") + + # Get breaches that affect a given account + resp = pyhibp.get_account_breaches(account="[email protected]", truncate_response=True) + ``` + + Developing + ---------- + This project is currently intended to be compatible with Python 2 and Python 3. As such, we use virtual environments via `pipenv`. + To develop or test, execute the following: + + ```bash + # Install the prerequisite virtual environment provider + $ pip install pipenv + # Initialize the pipenv environment and install the module within it + $ make dev + # To run PEP8, tests, and check the manifest + $ make tox + ``` + + Other commands can be found in the `Makefile`. + + Goals + ----- + - Synchronize to the latest HIBP API(s), implementing endpoint accessing functions where it makes sense. For instance, + in the interest of security, the ability to submit a SHA-1 to the Pwned Passwords endpoint is not implemented. See + "Regarding password checking" below for further details. + - For breaches and pastes, act as an intermediary; return the JSON as received from the service. + + Regarding password checking + --------------------------- + - For passwords, the option to supply a plaintext password to check is provided as an implementation convenience. + - For added security, `pwnedpasswords.is_password_breached()` only transmits the first five characters of the SHA-1 + hash to the Pwned Passwords API endpoint; a secure password will remain secure without disclosing the full hash. + +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+) +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Description-Content-Type: text/markdown +Provides-Extra: dev diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/src/pyhibp.egg-info/requires.txt new/pyhibp-4.0.0/src/pyhibp.egg-info/requires.txt --- old/pyhibp-3.1.0/src/pyhibp.egg-info/requires.txt 2019-06-30 11:17:15.000000000 +0200 +++ new/pyhibp-4.0.0/src/pyhibp.egg-info/requires.txt 2019-08-11 06:27:37.000000000 +0200 @@ -1,5 +1,4 @@ requests>=2.20.0 -six>=1.11.0 [dev] check-manifest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/test/conftest.py new/pyhibp-4.0.0/test/conftest.py --- old/pyhibp-3.1.0/test/conftest.py 2019-06-30 10:46:02.000000000 +0200 +++ new/pyhibp-4.0.0/test/conftest.py 2019-08-11 06:27:23.000000000 +0200 @@ -3,12 +3,28 @@ import pytest import pyhibp +from pyhibp import __version__ @pytest.fixture(autouse=True) -def dev_user_agent(monkeypatch): - ua_string = pyhibp.pyHIBP_USERAGENT - monkeypatch.setattr(pyhibp, 'pyHIBP_USERAGENT', ua_string + " (Testing Suite)") +def set_development_user_agent(monkeypatch): + """ + All calls to the HIBP/PwnedPasswords endpoints require a user agent to be set. So set one on all tests. + """ + ua_string = "pyHIBP/{version} (A Python interface to the public HIBP API; Testing suite)".format( + version=__version__.__version__, + ) + monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', ua_string) + + [email protected] +def use_fake_api_key(monkeypatch): + """ + As we have a decorator enforcing that an API key is set, we need to set a dummy key to test that our + parameter checking is working as intended. (So, we use this when we know we aren't going to be sending + a request that would otherwise be blocked for lack of a valid HIBP API key.) + """ + monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'hibp-api-key', 'NotARealKey') @pytest.fixture(name="sleep") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/test/test_pwnedpasswords.py new/pyhibp-4.0.0/test/test_pwnedpasswords.py --- old/pyhibp-3.1.0/test/test_pwnedpasswords.py 2019-06-30 10:46:02.000000000 +0200 +++ new/pyhibp-4.0.0/test/test_pwnedpasswords.py 2019-08-11 06:27:23.000000000 +0200 @@ -2,6 +2,7 @@ import pytest +import pyhibp from pyhibp import pwnedpasswords as pw # While the pwnedpasswords endpoint does not have a limit, be kind anyway. 1 second sleep. @@ -19,47 +20,19 @@ # is_password_breached(password=None, first_5_hash_chars=None, sha1_hash=None): with pytest.raises(AttributeError) as execinfo: pw.is_password_breached() - assert "One of password, sha1_hash, or first_5_hash_chars must be provided." in str(execinfo.value) + assert "Either password or sha1_hash must be provided." in str(execinfo.value) def test_password_not_string_raises(self): # is_password_breached(password=123, first_5_hash_chars=None, sha1_hash=None): with pytest.raises(AttributeError) as execinfo: pw.is_password_breached(password=123) - assert "password must be a string type." in str(execinfo.value) - - def test_first_5_hash_chars_not_string_raises(self): - # TODO: Deprecated: To be removed in next major release in favor of pw.suffix_search() - # is_password_breached(password=None, first_5_hash_chars=123, sha1_hash=None): - with pytest.raises(AttributeError) as execinfo: - pw.is_password_breached(first_5_hash_chars=123) - assert "first_5_hash_chars must be a string type." in str(execinfo.value) - - def test_first_5_hash_chars_not_length_five_raises(self): - # TODO: Deprecated: To be removed in next major release in favor of pw.suffix_search() - # is_password_breached(password=None, first_5_hash_chars="123456", sha1_hash=None): - with pytest.raises(AttributeError) as execinfo: - pw.is_password_breached(first_5_hash_chars="123456") - assert "first_5_hash_chars must be of length 5." in str(execinfo.value) + assert "password must be a string." in str(execinfo.value) def test_sha1_hash_not_string_raises(self): # is_password_breached(password=None, first_5_hash_chars=None, sha1_hash=123): with pytest.raises(AttributeError) as execinfo: pw.is_password_breached(sha1_hash=123) - assert "sha1_hash must be a string type." in str(execinfo.value) - - @pytest.mark.usefixtures('sleep') - def test_list_of_partial_hashes_returned_with_5chars(self): - # TODO: Deprecated: To be removed in next major release in favor of pw.suffix_search() - # is_password_breached(password=None, first_5_hash_chars=TEST_PASSWORD_SHA1_HASH[0:5], sha1_hash=None): - resp = pw.is_password_breached(first_5_hash_chars=TEST_PASSWORD_SHA1_HASH[0:5]) - assert isinstance(resp, list) - assert len(resp) > 100 - match_found = False - for entry in resp: - if TEST_PASSWORD_SHA1_HASH[5:] in entry.lower(): - match_found = True - break - assert match_found + assert "sha1_hash must be a string." in str(execinfo.value) @pytest.mark.usefixtures('sleep') def test_provide_password_to_function(self): @@ -91,13 +64,13 @@ # def suffix_search(hash_prefix=None): with pytest.raises(AttributeError) as execinfo: pw.suffix_search() - assert "hash_prefix must be a supplied, and be a string-type." in str(execinfo.value) + assert "hash_prefix must be a supplied, and be a string." in str(execinfo.value) def test_hash_prefix_not_string_raises(self): # def suffix_search(hash_prefix=123): with pytest.raises(AttributeError) as execinfo: pw.suffix_search(hash_prefix=123) - assert "hash_prefix must be a supplied, and be a string-type." in str(execinfo.value) + assert "hash_prefix must be a supplied, and be a string." in str(execinfo.value) def test_first_5_hash_chars_not_length_five_raises(self): # suffix_search(hash_prefix="123456"): @@ -120,3 +93,14 @@ match_found = True break assert match_found + + def test_user_agent_must_be_set_or_raise(self, monkeypatch): + """ + The HIBP backend requires a User-Agent; ensure we're forcing one to be set. + + Additionally, `is_password_breached` calls `suffix_search`, so we only need to check this function. + """ + monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', None) + with pytest.raises(RuntimeError) as execinfo: + pw.suffix_search(hash_prefix=TEST_PASSWORD_SHA1_HASH[0:5]) + assert "The User-Agent must be set. Call pyhibp.set_user_agent(ua=your_agent_string) first." in str(execinfo.value) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/test/test_pyHIBP.py new/pyhibp-4.0.0/test/test_pyHIBP.py --- old/pyhibp-3.1.0/test/test_pyHIBP.py 2019-06-30 10:46:02.000000000 +0200 +++ new/pyhibp-4.0.0/test/test_pyHIBP.py 2019-08-11 06:27:23.000000000 +0200 @@ -1,5 +1,3 @@ -import uuid - import pytest import pyhibp @@ -17,6 +15,14 @@ class TestGetAccountBreaches(object): @pytest.mark.usefixtures('sleep') + def test_get_breaches_api_key_must_be_specified_or_raise(self): + with pytest.raises(RuntimeError) as excinfo: + # Will raise because an API key has not been set on this request. + pyhibp.get_account_breaches(account=TEST_ACCOUNT) + assert "A HIBP API key is required for this call. Call pyhibp.set_api_key(key=your_key) first." in str(excinfo.value) + + @pytest.mark.skip("Unable to test due to lack of purchased API key") + @pytest.mark.usefixtures('sleep') def test_get_breaches_account(self): # get_account_breaches(account=TEST_ACCOUNT, domain=None, truncate_response=False, include_unverified=False): resp = pyhibp.get_account_breaches(account=TEST_ACCOUNT) @@ -25,6 +31,7 @@ assert len(resp) >= 20 assert isinstance(resp[0], dict) + @pytest.mark.skip("Unable to test due to lack of purchased API key") @pytest.mark.usefixtures('sleep') def test_get_breaches_account_with_domain(self): # get_account_breaches(account=TEST_ACCOUNT, domain=TEST_DOMAIN, truncate_response=False, include_unverified=False): @@ -35,6 +42,7 @@ assert isinstance(resp[0], dict) assert resp[0]['Name'] == TEST_DOMAIN_NAME + @pytest.mark.skip("Unable to test due to lack of purchased API key") @pytest.mark.usefixtures('sleep') def test_get_breaches_account_with_truncation(self): # get_account_breaches(account=TEST_ACCOUNT, domain=None, truncate_response=True, include_unverified=False): @@ -48,6 +56,7 @@ assert 'Name' in item assert 'DataClasses' not in item + @pytest.mark.skip("Unable to test due to lack of purchased API key") @pytest.mark.usefixtures('sleep') def test_get_breaches_retrieve_all_breaches_with_unverified(self): # get_account_breaches(account=TEST_ACCOUNT, domain=None, truncate_response=False, include_unverified=True): @@ -62,37 +71,48 @@ break assert has_unverified + @pytest.mark.skip("Unable to test due to lack of purchased API key") @pytest.mark.usefixtures('sleep') def test_get_breaches_return_false_if_no_accounts(self): # get_account_breaches(account=TEST_PASSWORD_SHA1_HASH, domain=None, truncate_response=False, include_unverified=False): resp = pyhibp.get_account_breaches(account=TEST_NONEXISTENT_ACCOUNT_NAME) assert not resp - assert isinstance(resp, bool) - # TODO: v4.0.0: - # assert not resp - # assert isinstance(resp, list) + assert isinstance(resp, list) + @pytest.mark.usefixtures('use_fake_api_key') def test_get_breaches_raise_if_account_is_not_specified(self): # get_account_breaches(account=1, domain=None, truncate_response=False, include_unverified=False): with pytest.raises(AttributeError) as excinfo: - # Will raise because the account must be a string (specifically, six.text_type) + # Will raise because the account must be a string pyhibp.get_account_breaches(account=None) assert "The account parameter must be specified, and must be a string" in str(excinfo.value) + @pytest.mark.usefixtures('use_fake_api_key') def test_get_breaches_raise_if_account_is_not_string(self): # get_account_breaches(account=1, domain=None, truncate_response=False, include_unverified=False): with pytest.raises(AttributeError) as excinfo: - # Will raise because the account must be a string (specifically, six.text_type) + # Will raise because the account must be a string pyhibp.get_account_breaches(account=1) assert "The account parameter must be specified, and must be a string" in str(excinfo.value) + @pytest.mark.usefixtures('use_fake_api_key') def test_get_breaches_raise_if_domain_is_not_string(self): # get_account_breaches(account=TEST_ACCOUNT, domain=1, truncate_response=False, include_unverified=False): with pytest.raises(AttributeError) as excinfo: - # Will raise because the domain must be a string (specifically, six.text_type) + # Will raise because the domain must be a string pyhibp.get_account_breaches(account=TEST_ACCOUNT, domain=1) assert "The domain parameter, if specified, must be a string" in str(excinfo.value) + @pytest.mark.usefixtures('use_fake_api_key') + def test_user_agent_must_be_set_or_raise(self, monkeypatch): + """ + The HIBP backend requires a User-Agent; ensure we're forcing one to be set on all functions + """ + monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', None) + with pytest.raises(RuntimeError) as execinfo: + pyhibp.get_account_breaches(account=TEST_ACCOUNT) + assert "The User-Agent must be set. Call pyhibp.set_user_agent(ua=your_agent_string) first." in str(execinfo.value) + class TestGetAllBreaches(object): @pytest.mark.usefixtures('sleep') @@ -117,18 +137,24 @@ def test_get_all_breaches_false_if_domain_does_not_exist(self): resp = pyhibp.get_all_breaches(domain=TEST_NONEXISTENT_ACCOUNT_NAME) assert not resp - assert isinstance(resp, bool) - # TODO: v4.0.0: - # assert not resp - # assert isinstance(resp, list) + assert isinstance(resp, list) def test_get_all_breaches_raise_if_not_string(self): # def get_all_breaches(domain=1): with pytest.raises(AttributeError) as excinfo: - # Will raise because the domain must be a string (specifically, six.text_type) + # Will raise because the domain must be a string pyhibp.get_all_breaches(domain=1) assert "The domain parameter, if specified, must be a string" in str(excinfo.value) + def test_user_agent_must_be_set_or_raise(self, monkeypatch): + """ + The HIBP backend requires a User-Agent; ensure we're forcing one to be set on all functions + """ + monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', None) + with pytest.raises(RuntimeError) as execinfo: + pyhibp.get_all_breaches() + assert "The User-Agent must be set. Call pyhibp.set_user_agent(ua=your_agent_string) first." in str(execinfo.value) + class TestGetSingleBreach(object): @pytest.mark.usefixtures('sleep') @@ -142,11 +168,8 @@ def test_get_single_breach_when_breach_does_not_exist(self): # get_single_breach(breach_name="ThisShouldNotExist") resp = pyhibp.get_single_breach(breach_name="ThisShouldNotExist") - # Boolean False will be returned from the above (as there is no breach named what we gave it). assert not resp - # TODO: v4.0.0: - # assert not resp - # assert isinstance(resp, dict) + assert isinstance(resp, dict) def test_get_single_breach_raise_when_breach_name_not_specified(self): # get_single_breach() @@ -158,14 +181,32 @@ def test_get_single_breach_raise_when_breach_name_is_not_a_string(self): # get_single_breach(breach_name=1) with pytest.raises(AttributeError) as excinfo: - # Will raise because the breach_name must be a string (specifically, six.text_type) + # Will raise because the breach_name must be a string pyhibp.get_single_breach(breach_name=1) assert "The breach_name must be specified, and be a string" in str(excinfo.value) + def test_user_agent_must_be_set_or_raise(self, monkeypatch): + """ + The HIBP backend requires a User-Agent; ensure we're forcing one to be set on all functions + """ + monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', None) + with pytest.raises(RuntimeError) as execinfo: + pyhibp.get_single_breach(breach_name=TEST_DOMAIN_NAME) + assert "The User-Agent must be set. Call pyhibp.set_user_agent(ua=your_agent_string) first." in str(execinfo.value) + class TestGetPastes(object): @pytest.mark.usefixtures('sleep') + def test_get_pastes_api_key_must_be_specified_or_raise(self): + with pytest.raises(RuntimeError) as excinfo: + # Will raise because an API key has not been set on this request. + pyhibp.get_pastes(email_address=TEST_ACCOUNT) + assert "A HIBP API key is required for this call. Call pyhibp.set_api_key(key=your_key) first." in str(excinfo.value) + + @pytest.mark.skip("Unable to test due to lack of purchased API key") + @pytest.mark.usefixtures('sleep') def test_get_pastes(self): + print(pyhibp.pyHIBP_HEADERS) # get_pastes(email_address=TEST_ACCOUNT): resp = pyhibp.get_pastes(email_address=TEST_ACCOUNT) # The return value is a list, containing multiple dicts (1 or more) @@ -173,30 +214,50 @@ for item in resp: assert isinstance(item, dict) + @pytest.mark.skip("Unable to test due to lack of purchased API key") + @pytest.mark.usefixtures('sleep') def test_get_pastes_return_false_if_no_account(self): + print(pyhibp.pyHIBP_HEADERS) # get_pastes(email_address=TEST_ACCOUNT): resp = pyhibp.get_pastes(email_address=TEST_NONEXISTENT_ACCOUNT_NAME + "@example.invalid") assert not resp - assert isinstance(resp, bool) - # TODO: v4.0.0: - # assert not resp - # assert isinstance(resp, list) + assert isinstance(resp, list) - @pytest.mark.usefixtures('sleep') + @pytest.mark.usefixtures('use_fake_api_key') def test_get_pastes_raise_if_email_not_specified(self): # get_pastes(): with pytest.raises(AttributeError) as excinfo: pyhibp.get_pastes() assert "The email address supplied must be provided, and be a string" in str(excinfo.value) + @pytest.mark.usefixtures('use_fake_api_key') def test_get_pastes_raise_if_email_not_string(self): # get_pastes(email_address=1): with pytest.raises(AttributeError) as excinfo: pyhibp.get_pastes(email_address=1) assert "The email address supplied must be provided, and be a string" in str(excinfo.value) + @pytest.mark.usefixtures('use_fake_api_key') + def test_user_agent_must_be_set_or_raise(self, monkeypatch): + """ + The HIBP backend requires a User-Agent; ensure we're forcing one to be set on all functions + """ + monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', None) + with pytest.raises(RuntimeError) as execinfo: + pyhibp.get_pastes(email_address=TEST_ACCOUNT) + assert "The User-Agent must be set. Call pyhibp.set_user_agent(ua=your_agent_string) first." in str(execinfo.value) + class TestGetDataClasses(object): + def test_user_agent_must_be_set_or_raise(self, monkeypatch): + """ + The HIBP backend requires a User-Agent; ensure we're forcing one to be set on all functions + """ + monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', None) + with pytest.raises(RuntimeError) as execinfo: + pyhibp.get_data_classes() + assert "The User-Agent must be set. Call pyhibp.set_user_agent(ua=your_agent_string) first." in str(execinfo.value) + @pytest.mark.usefixtures('sleep') def test_get_data_classes(self): # get_data_classes(): @@ -207,28 +268,7 @@ class TestMiscellaneous(object): - @pytest.mark.xfail(reason="The rate limit exists in the API docs, but responses are cached, and even attempting to manually (via browser) hit the limit isn't happening.") - @pytest.mark.usefixtures('sleep') - def test_raise_if_rate_limit_exceeded(self): - """ The API will respond the same to all exceeded rate limits across all endpoints """ - # The rate limit exists, however all responses are cached; so we need to generate some random "accounts". - rand_accts = ["{0}@test-suite.pyhibp.example.com".format(str(uuid.uuid4())) for j in range(4)] - - with pytest.raises(RuntimeError) as excinfo: - for item in rand_accts: - pyhibp.get_account_breaches(account=item, truncate_response=True) - assert "HTTP 429" in str(excinfo.value) - - @pytest.mark.usefixtures('sleep') - def test_raise_if_useragent_is_not_set(self, monkeypatch): - # This should never be encountered normally, since we have the module-level variable/constant; - # That said, test it, since we can, and since we might as well cover the line of code. - head = {'User-Agent': ''} - monkeypatch.setattr(pyhibp, 'pyHIBP_HEADERS', head) - with pytest.raises(RuntimeError) as excinfo: - pyhibp.get_account_breaches(account="{0}@test-suite.pyhibp.example.com".format(str(uuid.uuid4()))) - assert "HTTP 403" in str(excinfo.value) - + @pytest.mark.skip("Unable to test due to lack of purchased API key") @pytest.mark.usefixtures('sleep') def test_raise_if_invalid_format_submitted(self): # For example, if a null (0x00) character is submitted to an endpoint. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pyhibp-3.1.0/tox.ini new/pyhibp-4.0.0/tox.ini --- old/pyhibp-3.1.0/tox.ini 2019-06-30 10:46:02.000000000 +0200 +++ new/pyhibp-4.0.0/tox.ini 2019-08-11 06:27:23.000000000 +0200 @@ -1,9 +1,8 @@ [tox] -envlist = py{27,35,36,37} +envlist = py{35,36,37} [testenv] basepython = - py27: python2.7 py35: python3.5 py36: python3.6 py37: python3.7
