From 97ef42c849cd7cb50ae54e9399b524ad680ad84e Mon Sep 17 00:00:00 2001 From: Dmitry Volodin Date: Fri, 17 Nov 2023 16:16:39 +0100 Subject: [PATCH] ACMEClient, types, and exceptions have been renamed to snake-case. --- .github/ISSUE_TEMPLATE/bug-report.yml | 2 +- CHANGELOG.md | 10 +- README.md | 22 +-- docs/examples/acme_register.md | 8 +- docs/examples/acme_sign.md | 22 +-- docs/examples/get_csr.md | 4 +- docs/examples/get_private_key.md | 6 +- docs/index.md | 22 +-- docs/testing.md | 2 +- examples/acme_register.py | 6 +- examples/acme_sign.py | 12 +- examples/get_csr.py | 4 +- examples/get_private_key.py | 4 +- src/gufo/acme/clients/base.py | 268 +++++++++++++------------- src/gufo/acme/clients/dav.py | 30 +-- src/gufo/acme/clients/powerdns.py | 17 +- src/gufo/acme/clients/web.py | 26 +-- src/gufo/acme/error.py | 26 +-- src/gufo/acme/types.py | 10 +- tests/clients/test_base.py | 150 +++++++------- tests/clients/test_dav.py | 24 +-- tests/clients/test_powerdns.py | 8 +- tests/clients/test_web.py | 10 +- tests/clients/utils.py | 6 +- 24 files changed, 349 insertions(+), 350 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index ec16153..15a6d14 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -25,7 +25,7 @@ body: should be self-contained, i.e., can be copy-pasted into the Python interpreter or run as-is via `python myproblem.py`. placeholder: | - from gufo.acme.clients.base import ACMEClient + from gufo.acme.clients.base import AcmeClient << your code here >> render: python validations: diff --git a/CHANGELOG.md b/CHANGELOG.md index 63bd603..1c0c080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,13 +14,13 @@ To see unreleased changes, please see the [CHANGELOG on the master branch](https ## Added -* DAVACMEClient: http-01 fulfillment using WebDAV +* DavAcmeClient: http-01 fulfillment using WebDAV * PowerDnsAcmeClient: dns-01 fulfillment using PowerDNS. -* WEBACMEClient: http-01 fulfillment using static files. +* WEBAcmeClient: http-01 fulfillment using static files. ## Changed -* ACMEClient has been moved into `gufo.acme.clients.base`. +* AcmeClient has been moved into `gufo.acme.clients.base`. ## Fixed @@ -30,8 +30,8 @@ To see unreleased changes, please see the [CHANGELOG on the master branch](https ### Fixed -* Fixed `ACMEClient.from_state()` to return a proper subclass. -* Fixed type annotation for `ACMEClient.__aenter__()` in subclasses. +* Fixed `AcmeClient.from_state()` to return a proper subclass. +* Fixed type annotation for `AcmeClient.__aenter__()` in subclasses. ## 0.1.0 - 2023-11-15 diff --git a/README.md b/README.md index c23daf0..56d54e9 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,11 @@ simplifies the protocol complexity with a straightforward and robust API. Gufo ACME contains various clients which can be applied to your tasks: -* ACMEClient - base client to implement any fulfillment functionality +* AcmeClient - base client to implement any fulfillment functionality by creating subclasses. -* DAVACMEClient - http-01 fulfillment using WebDAV methods. +* DavAcmeClient - http-01 fulfillment using WebDAV methods. * PowerDnsAcmeClient - dns-01 PowerDNS fulfillment. -* WebACMEClient - http-01 static file fulfillment. +* WebAcmeClient - http-01 static file fulfillment. ## Supported Certificate Authorities @@ -40,8 +40,8 @@ Gufo ACME contains various clients which can be applied to your tasks: Create an account and store state to the file. ``` python -client_key = ACMEClient.get_key() -async with ACMEClient(DIRECTORY, key=client_key) as client: +client_key = AcmeClient.get_key() +async with AcmeClient(DIRECTORY, key=client_key) as client: await client.new_account(email) state = client.get_state() with open(client_state_path, "wb") as fp: @@ -52,14 +52,14 @@ with open(client_state_path, "wb") as fp: To generate a private key in PEM format. ``` python -private_key = ACMEClient.get_domain_private_key() +private_key = AcmeClient.get_domain_private_key() ``` ### Generate CSR To generate a certificate signing request. ``` python -csr = ACMEClient.get_domain_csr(domain, private_key) +csr = AcmeClient.get_domain_csr(domain, private_key) ``` ### Sign Certificate @@ -70,9 +70,9 @@ Sign the certificate using `http-01` challenge: CHALLENGE_DIR = "/www/acme/" -class SignACMEClient(ACMEClient): +class SignAcmeClient(AcmeClient): async def fulfill_http_01( - self, domain: str, challenge: ACMEChallenge + self, domain: str, challenge: AcmeChallenge ) -> bool: v = self.get_key_authorization(challenge) with open(os.path.join(CHALLENGE_DIR, challenge.token), "wb") as fp: @@ -80,12 +80,12 @@ class SignACMEClient(ACMEClient): return True async def clear_http_01( - self: ACMEClient, domain: str, challenge: ACMEChallenge + self: AcmeClient, domain: str, challenge: AcmeChallenge ) -> None: os.unlink(os.path.join(CHALLENGE_DIR, challenge.token)) ... -async with SignACMEClient.from_state(state) as client: +async with SignAcmeClient.from_state(state) as client: cert = await client.sign(domain, csr) ``` diff --git a/docs/examples/acme_register.md b/docs/examples/acme_register.md index 1e5aab2..24937ff 100644 --- a/docs/examples/acme_register.md +++ b/docs/examples/acme_register.md @@ -18,7 +18,7 @@ The code is straightforward: --8<-- "examples/acme_register.py" ``` -ACMEClient is an asynchronous client, so we +AcmeClient is an asynchronous client, so we need `asyncio.run()` function to launch it. @@ -37,7 +37,7 @@ Import `sys` module to parse the CLI argument. --8<-- "examples/acme_register.py" ``` -Then we import an `ACMEClient` itself. +Then we import an `AcmeClient` itself. ``` py title="acme_register.py" linenums="1" hl_lines="6" --8<-- "examples/acme_register.py" @@ -71,13 +71,13 @@ the following parameters: ``` The client uses secret key to sign all communications to the server. Later, this key will be bound to account. -We use `ACMEClient.get_key()` function to generate +We use `AcmeClient.get_key()` function to generate a new key. ``` py title="acme_register.py" linenums="1" hl_lines="11" --8<-- "examples/acme_register.py" ``` -`ACMEClient` requires two mandatory parameters: +`AcmeClient` requires two mandatory parameters: * ACME Directory URL. * The client key. diff --git a/docs/examples/acme_sign.md b/docs/examples/acme_sign.md index f457eef..095fdd8 100644 --- a/docs/examples/acme_sign.md +++ b/docs/examples/acme_sign.md @@ -50,7 +50,7 @@ The code is straightforward: --8<-- "examples/acme_sign.py::6" ``` -ACMEClient is an asynchronous client, so we +AcmeClient is an asynchronous client, so we need `asyncio.run()` function to launch it. ``` py title="acme_sign.py" linenums="1" hl_lines="2" @@ -74,12 +74,12 @@ Import `sys` module to parse the CLI argument. --8<-- "examples/acme_sign.py::6" ``` -Then we import an `ACMEClient` itself. +Then we import an `AcmeClient` itself. ``` py title="acme_sign.py" linenums="1" hl_lines="6" --8<-- "examples/acme_sign.py::6" ``` -We also need an `ACMEChallenge` type. +We also need an `AcmeChallenge` type. ``` py title="acme_sign.py" linenums="6" hl_lines="3" --8<-- "examples/acme_sign.py:6:11" @@ -101,7 +101,7 @@ We need to provide the implementation of the challenge fulfillment. The Gufo ACME's API provides special methods which can be overriden in subclasses to implement the desired behavior. So we are creating -a subclass of `ACMEClient`. +a subclass of `AcmeClient`. ``` py title="acme_sign.py" linenums="11" hl_lines="2 3 4" --8<-- "examples/acme_sign.py:11:23" @@ -111,7 +111,7 @@ We're implementing `http-01` challenge, so we need to override so we use any async function inside. It accepts two parameters: * `domain` - a domain name. -* `challenge` - a ACMEChallenge structure, which has +* `challenge` - a AcmeChallenge structure, which has a `token` field, containing challenge token. Function returns `True` when fulfillment has beed processed correctly, @@ -123,7 +123,7 @@ or `False`, if we wan't provide a fulfillment. According the ACME protocol, we need to place a specially formed data to prove our authority. The data contain challenge token and the fingerprint of the client's key. The calculation may be tricky, -but Gufo ACME provides a `ACMEClient.get_key_authorization()` method, +but Gufo ACME provides a `AcmeClient.get_key_authorization()` method, which performs all necessary calculations. So we pass `challenge` parameter and grab an authorization data as value of the variable `v`. @@ -143,13 +143,13 @@ and ready to start validation. --8<-- "examples/acme_sign.py:11:23" ``` The ACME protocol definition exlicitly notes that client may -clean up prepared data after the validation. `ACMEClient` +clean up prepared data after the validation. `AcmeClient` allows to add own cleanup code by overriding `cleanup_*` methods. In our case we're overriding `clear_http_01` method. Just like `fulfill_http_01`, it accepts two parameters: * `domain` - a domain name. -* `challenge` - a ACMEChallenge structure, which has +* `challenge` - a AcmeChallenge structure, which has a `token` field, containing challenge token. ``` py title="acme_sign.py" linenums="11" hl_lines="13" @@ -189,9 +189,9 @@ is binary so we're opening the file in `rb` mode. ``` py title="acme_sign.py" linenums="26" hl_lines="8" --8<-- "examples/acme_sign.py:26:36" ``` -We're instantiating `ACMEClient` directly from state by +We're instantiating `AcmeClient` directly from state by using `from_state()` method. Note, we're restoring state -not into the `ACMEClient`, but in our `SignACMEClient` subclass. +not into the `AcmeClient`, but in our `SignAcmeClient` subclass. The new `client` instance loads the private key, directory, and account information directly from state. @@ -199,7 +199,7 @@ directly from state. ``` py title="acme_sign.py" linenums="26" hl_lines="9" --8<-- "examples/acme_sign.py:26:36" ``` -And finally, we call an `ACMEClient.sign` method, which +And finally, we call an `AcmeClient.sign` method, which accepts domain name and CSR. The `sign` method simple hides all the protocol's complexity and simply returns us a signed certificate in PEM format. diff --git a/docs/examples/get_csr.md b/docs/examples/get_csr.md index 8e880d9..8c31256 100644 --- a/docs/examples/get_csr.md +++ b/docs/examples/get_csr.md @@ -29,7 +29,7 @@ Import `sys` module to parse the CLI argument. --8<-- "examples/get_csr.py" ``` -Then we import an `ACMEClient` itself. +Then we import an `AcmeClient` itself. ``` py title="get_csr.py" linenums="1" hl_lines="6" --8<-- "examples/get_csr.py" @@ -53,7 +53,7 @@ The `pk` variable contains our private key. ``` py title="get_csr.py" linenums="1" hl_lines="9" --8<-- "examples/get_csr.py" ``` -`ACMEClient.get_domain_csr()` function generates +`AcmeClient.get_domain_csr()` function generates a CSR in PEM format. It aceepts requred parameters: * `domain` - domain name diff --git a/docs/examples/get_private_key.md b/docs/examples/get_private_key.md index 9de356b..5c37783 100644 --- a/docs/examples/get_private_key.md +++ b/docs/examples/get_private_key.md @@ -27,7 +27,7 @@ Import `sys` module to parse the CLI argument. --8<-- "examples/get_private_key.py" ``` -Then we import an `ACMEClient` itself. +Then we import an `AcmeClient` itself. ``` py title="get_private_key.py" linenums="1" hl_lines="6" --8<-- "examples/get_private_key.py" @@ -39,12 +39,12 @@ a private key. ``` py title="get_private_key.py" linenums="1" hl_lines="7" --8<-- "examples/get_private_key.py" ``` -`ACMEClient.get_domain_private_key()` function generates +`AcmeClient.get_domain_private_key()` function generates a private key in PEM format. It assepts an optional parameter which defines a RSA key length. The default is 4096, which is suitable for our applications. This function is the static method, so we don't need to instantiate an -`ACMEClient`. +`AcmeClient`. ``` py title="get_private_key.py" linenums="1" hl_lines="8 9" --8<-- "examples/get_private_key.py" diff --git a/docs/index.md b/docs/index.md index 7657109..9fe5acf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,11 +20,11 @@ simplifies the protocol complexity with a straightforward and robust API. Gufo ACME contains various clients which can be applied to your tasks: -* [ACMEClient][gufo.acme.clients.base.ACMEClient] - base client to implement any fulfillment functionality +* [AcmeClient][gufo.acme.clients.base.AcmeClient] - base client to implement any fulfillment functionality by creating subclasses. -* [DAVACMEClient][gufo.acme.clients.dav.DAVACMEClient] - http-01 fulfillment using WebDAV methods. +* [DavAcmeClient][gufo.acme.clients.dav.DavAcmeClient] - http-01 fulfillment using WebDAV methods. * [PowerDnsAcmeClient][gufo.acme.clients.powerdns.PowerDnsAcmeClient] - dns-01 PowerDNS fulfillment. -* [WebACMEClient][gufo.acme.clients.web.WebACMEClient] - http-01 static file fulfillment. +* [WebAcmeClient][gufo.acme.clients.web.WebAcmeClient] - http-01 static file fulfillment. ## Supported Certificate Authorities @@ -37,8 +37,8 @@ Gufo ACME contains various clients which can be applied to your tasks: Create an account and store state to the file. ``` python -client_key = ACMEClient.get_key() -async with ACMEClient(DIRECTORY, key=client_key) as client: +client_key = AcmeClient.get_key() +async with AcmeClient(DIRECTORY, key=client_key) as client: await client.new_account(email) state = client.get_state() with open(client_state_path, "wb") as fp: @@ -49,14 +49,14 @@ with open(client_state_path, "wb") as fp: To generate a private key in PEM format. ``` python -private_key = ACMEClient.get_domain_private_key() +private_key = AcmeClient.get_domain_private_key() ``` ### Generate CSR To generate a certificate signing request. ``` python -csr = ACMEClient.get_domain_csr(domain, private_key) +csr = AcmeClient.get_domain_csr(domain, private_key) ``` ### Sign Certificate @@ -67,9 +67,9 @@ Sign the certificate using `http-01` challenge: CHALLENGE_DIR = "/www/acme/" -class SignACMEClient(ACMEClient): +class SignAcmeClient(AcmeClient): async def fulfill_http_01( - self, domain: str, challenge: ACMEChallenge + self, domain: str, challenge: AcmeChallenge ) -> bool: v = self.get_key_authorization(challenge) with open(os.path.join(CHALLENGE_DIR, challenge.token), "wb") as fp: @@ -77,12 +77,12 @@ class SignACMEClient(ACMEClient): return True async def clear_http_01( - self: ACMEClient, domain: str, challenge: ACMEChallenge + self: AcmeClient, domain: str, challenge: AcmeChallenge ) -> None: os.unlink(os.path.join(CHALLENGE_DIR, challenge.token)) ... -async with SignACMEClient.from_state(state) as client: +async with SignAcmeClient.from_state(state) as client: cert = await client.sign(domain, csr) ``` diff --git a/docs/testing.md b/docs/testing.md index c12bbf9..337d966 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -78,7 +78,7 @@ to run tests in the CI environment. On local environments, the test is skipped b To enable the test in your local environment, additional infrastructure is needed. -### DAVACMEClient +### DavAcmeClient 1. Have control over a DNS zone (later ``). 2. Set up an Nginx server. diff --git a/examples/acme_register.py b/examples/acme_register.py index 80d28d6..bb1168a 100644 --- a/examples/acme_register.py +++ b/examples/acme_register.py @@ -1,14 +1,14 @@ import asyncio import sys -from gufo.acme.clients.base import ACMEClient +from gufo.acme.clients.base import AcmeClient DIRECTORY = "https://acme-staging-v02.api.letsencrypt.org/directory" async def main(email: str, client_state_path: str) -> None: - client_key = ACMEClient.get_key() - async with ACMEClient(DIRECTORY, key=client_key) as client: + client_key = AcmeClient.get_key() + async with AcmeClient(DIRECTORY, key=client_key) as client: await client.new_account(email) state = client.get_state() with open(client_state_path, "wb") as fp: diff --git a/examples/acme_sign.py b/examples/acme_sign.py index 2f9acd8..5f1539d 100644 --- a/examples/acme_sign.py +++ b/examples/acme_sign.py @@ -2,15 +2,15 @@ import os import sys -from gufo.acme.clients.base import ACMEClient -from gufo.acme.types import ACMEChallenge +from gufo.acme.clients.base import AcmeClient +from gufo.acme.types import AcmeChallenge CHALLENGE_DIR = "/www/acme/" -class SignACMEClient(ACMEClient): +class SignAcmeClient(AcmeClient): async def fulfill_http_01( - self, domain: str, challenge: ACMEChallenge + self, domain: str, challenge: AcmeChallenge ) -> bool: v = self.get_key_authorization(challenge) with open(os.path.join(CHALLENGE_DIR, challenge.token), "wb") as fp: @@ -18,7 +18,7 @@ async def fulfill_http_01( return True async def clear_http_01( - self: ACMEClient, domain: str, challenge: ACMEChallenge + self: AcmeClient, domain: str, challenge: AcmeChallenge ) -> None: os.unlink(os.path.join(CHALLENGE_DIR, challenge.token)) @@ -30,7 +30,7 @@ async def main( state = fp.read() with open(csr_path, "wb") as fp: csr = fp.read() - async with SignACMEClient.from_state(state) as client: + async with SignAcmeClient.from_state(state) as client: cert = await client.sign(domain, csr) with open(cert_path, "wb") as fp: fp.write(cert) diff --git a/examples/get_csr.py b/examples/get_csr.py index 33d3a76..0609fed 100644 --- a/examples/get_csr.py +++ b/examples/get_csr.py @@ -1,12 +1,12 @@ import sys -from gufo.acme.clients.base import ACMEClient +from gufo.acme.clients.base import AcmeClient def main(domain: str, private_key_path: str, csr_path: str) -> None: with open(private_key_path, "rb") as fp: pk = fp.read() - csr = ACMEClient.get_domain_csr(domain, pk) + csr = AcmeClient.get_domain_csr(domain, pk) with open(csr_path, "wb") as fp: fp.write(csr) diff --git a/examples/get_private_key.py b/examples/get_private_key.py index 328f74f..b88ed8d 100644 --- a/examples/get_private_key.py +++ b/examples/get_private_key.py @@ -1,10 +1,10 @@ import sys -from gufo.acme.clients.base import ACMEClient +from gufo.acme.clients.base import AcmeClient def main(path: str) -> None: - pk = ACMEClient.get_domain_private_key() + pk = AcmeClient.get_domain_private_key() with open(path, "wb") as fp: fp.write(pk) diff --git a/src/gufo/acme/clients/base.py b/src/gufo/acme/clients/base.py index 30bc9ea..b7d5940 100644 --- a/src/gufo/acme/clients/base.py +++ b/src/gufo/acme/clients/base.py @@ -1,9 +1,9 @@ # --------------------------------------------------------------------- -# Gufo ACME: ACMEClient implementation +# Gufo ACME: AcmeClient implementation # --------------------------------------------------------------------- # Copyright (C) 2023, Gufo Labs # --------------------------------------------------------------------- -"""An ACMEClient implementation.""" +"""An AcmeClient implementation.""" # Python modules import asyncio import json @@ -44,42 +44,42 @@ from .. import __version__ from ..acme import AcmeJWS from ..error import ( - ACMEAlreadyRegistered, - ACMEAuthorizationError, - ACMEBadNonceError, - ACMECertificateError, - ACMEConnectError, - ACMEError, - ACMEFulfillmentFailed, - ACMENotRegistredError, - ACMERateLimitError, - ACMETimeoutError, - ACMEUnauthorizedError, - ACMEUndecodableError, + AcmeAlreadyRegistered, + AcmeAuthorizationError, + AcmeBadNonceError, + AcmeCertificateError, + AcmeConnectError, + AcmeError, + AcmeFulfillmentFailed, + AcmeNotRegistredError, + AcmeRateLimitError, + AcmeTimeoutError, + AcmeUnauthorizedError, + AcmeUndecodableError, ) from ..log import logger -from ..types import ACMEAuthorization, ACMEChallenge, ACMEDirectory, ACMEOrder +from ..types import AcmeAuthorization, AcmeChallenge, AcmeDirectory, AcmeOrder BAD_REQUEST = 400 T = TypeVar("T") -CT = TypeVar("CT", bound="ACMEClient") +CT = TypeVar("CT", bound="AcmeClient") -class ACMEClient(object): +class AcmeClient(object): """ ACME Client. Examples: Create new account: ``` python - async with ACMEClient(directory, key=key) as client: + async with AcmeClient(directory, key=key) as client: uri = await client.new_account("test@example.com") ``` Sign an CSR: ``` python - class SignClient(ACMEClient): + class SignClient(AcmeClient): async def fulfill_http_01( - self, domain: str, challenge: ACMEChallenge + self, domain: str, challenge: AcmeChallenge ) -> bool: # do something useful return True @@ -99,11 +99,11 @@ async def fulfill_http_01( Args: directory_url: An URL to ACME directory. key: JWK private key. The compatible key may be generated - by the [gufo.acme.clients.base.ACMEClient.get_key][] function. + by the [gufo.acme.clients.base.AcmeClient.get_key][] function. alg: Signing algorithm to use. account_url: Optional ACME account URL, cotaining the stored result of the previous call to the - [gufo.acme.clients.base.ACMEClient.new_account][] function. + [gufo.acme.clients.base.AcmeClient.new_account][] function. timeout: Network requests timeout in seconds. user_agent: Override default User-Agent header. """ @@ -115,7 +115,7 @@ async def fulfill_http_01( DEFAULT_SIGNATURE = RS256 def __init__( - self: "ACMEClient", + self: "AcmeClient", directory_url: str, *, key: JWK, @@ -125,7 +125,7 @@ def __init__( user_agent: Optional[str] = None, ) -> None: self._directory_url = directory_url - self._directory: Optional[ACMEDirectory] = None + self._directory: Optional[AcmeDirectory] = None self._key = key self._alg = alg or self.DEFAULT_SIGNATURE self._account_url = account_url @@ -139,14 +139,14 @@ async def __aenter__(self: CT) -> CT: Examples: ``` py - async with ACMEClient(....) as client: + async with AcmeClient(....) as client: ... ``` """ return self async def __aexit__( - self: "ACMEClient", + self: "AcmeClient", exc_t: Optional[Type[BaseException]], exc_v: Optional[BaseException], exc_tb: Optional[TracebackType], @@ -154,14 +154,14 @@ async def __aexit__( """Asynchronous context exit.""" return - def is_bound(self: "ACMEClient") -> bool: + def is_bound(self: "AcmeClient") -> bool: """ Check if the client is bound to the account. The client may be bound to account either: * By setting `account_url` in constructor. - * By calling [gufo.acme.clients.base.ACMEClient.new_account][] + * By calling [gufo.acme.clients.base.AcmeClient.new_account][] Returns: True - if the client is bound to account, @@ -169,27 +169,27 @@ def is_bound(self: "ACMEClient") -> bool: """ return self._account_url is not None - def _check_bound(self: "ACMEClient") -> None: + def _check_bound(self: "AcmeClient") -> None: """ Check the client is bound to account. Raises: - ACMENotRegistredError: if client is not bound. + AcmeNotRegistredError: if client is not bound. """ if not self.is_bound(): - raise ACMENotRegistredError + raise AcmeNotRegistredError - def _check_unbound(self: "ACMEClient") -> None: + def _check_unbound(self: "AcmeClient") -> None: """ Check the client is not bound to account. Raises: - ACMEAlreadyRegistered: if client is bound. + AcmeAlreadyRegistered: if client is bound. """ if self.is_bound(): - raise ACMEAlreadyRegistered + raise AcmeAlreadyRegistered - def _get_client(self: "ACMEClient") -> httpx.AsyncClient: + def _get_client(self: "AcmeClient") -> httpx.AsyncClient: """ Get a HTTP client instance. @@ -208,20 +208,20 @@ async def _wait_for(fut: Coroutine[Any, Any, T], timeout: float) -> T: try: return await asyncio.wait_for(fut, timeout) except asyncio.TimeoutError as e: - raise ACMETimeoutError from e + raise AcmeTimeoutError from e - async def _get_directory(self: "ACMEClient") -> ACMEDirectory: + async def _get_directory(self: "AcmeClient") -> AcmeDirectory: """ Get and ACME directory. Caches response to fetch request only once. Returns: - ACMEDirectory instance containing URLs + AcmeDirectory instance containing URLs for ACME server. Raises: - ACMEError: In case of the errors. + AcmeError: In case of the errors. """ if self._directory is not None: return self._directory @@ -234,10 +234,10 @@ async def _get_directory(self: "ACMEClient") -> ACMEDirectory: client.get(self._directory_url), self._timeout ) except httpx.HTTPError as e: - raise ACMEConnectError from e + raise AcmeConnectError from e self._check_response(r) data = r.json() - self._directory = ACMEDirectory( + self._directory = AcmeDirectory( new_account=data["newAccount"], new_nonce=data.get("newNonce"), new_order=data["newOrder"], @@ -260,7 +260,7 @@ def _email_to_contacts(email: Union[str, Iterable[str]]) -> List[str]: return [f"mailto:{m}" for m in email] async def new_account( - self: "ACMEClient", email: Union[str, Iterable[str]] + self: "AcmeClient", email: Union[str, Iterable[str]] ) -> str: """ Create new account. @@ -272,14 +272,14 @@ async def new_account( Create an account with single contact email: ``` python - async with ACMEClient(directory, key=key) as client: + async with AcmeClient(directory, key=key) as client: uri = await client.new_account("test@example.com") ``` Create an account with multiple contact emails: ``` python - async with ACMEClient(directory, key=key) as client: + async with AcmeClient(directory, key=key) as client: uri = await client.new_account([ "ca@example.com", "boss@example.com" @@ -294,8 +294,8 @@ async def new_account( to the ACME client. Raises: - ACMEError: In case of the errors. - ACMEAlreadyRegistered: If an client is already bound to account. + AcmeError: In case of the errors. + AcmeAlreadyRegistered: If an client is already bound to account. """ # Build contacts contacts = self._email_to_contacts(email) @@ -315,7 +315,7 @@ async def new_account( self._account_url = resp.headers["Location"] return self._account_url - async def deactivate_account(self: "ACMEClient") -> None: + async def deactivate_account(self: "AcmeClient") -> None: """ Deactivate account. @@ -324,7 +324,7 @@ async def deactivate_account(self: "ACMEClient") -> None: issuance or access resources related to the account, such as orders or authorizations. - To call `deactivate_account` ACMEClient must be bound + To call `deactivate_account` AcmeClient must be bound to acount either via `account_url` option or via `new_account` call. After successfully processing a client will be unbound from account. @@ -333,7 +333,7 @@ async def deactivate_account(self: "ACMEClient") -> None: Deactivate account: ``` python - async with ACMEClient( + async with AcmeClient( directory, key=key, account_url=url ) as client: @@ -341,8 +341,8 @@ async def deactivate_account(self: "ACMEClient") -> None: ``` Raises: - ACMEError: In case of the errors. - ACMENotRegistred: If the client is not bound to account. + AcmeError: In case of the errors. + AcmeNotRegistred: If the client is not bound to account. """ logger.warning("Deactivating account: %s", self._account_url) # Check account is really bound @@ -374,8 +374,8 @@ def _domain_to_identifiers( return [{"type": "dns", "value": d} for d in domain] async def new_order( - self: "ACMEClient", domain: Union[str, Iterable[str]] - ) -> ACMEOrder: + self: "AcmeClient", domain: Union[str, Iterable[str]] + ) -> AcmeOrder: """ Create new order. @@ -389,7 +389,7 @@ async def new_order( Order for single domain: ``` python - async with ACMEClient( + async with AcmeClient( directory, key=key, account_url=account_url @@ -400,7 +400,7 @@ async def new_order( Order for multiple domains: ``` py - async with ACMEClient( + async with AcmeClient( directory, key=key, account_url=account_url @@ -416,10 +416,10 @@ async def new_order( yielding domains. Returns: - An ACMEOrder object. + An AcmeOrder object. Raises: - ACMEError: In case of the errors. + AcmeError: In case of the errors. """ # Expand identifiers identifiers = self._domain_to_identifiers(domain) @@ -434,17 +434,17 @@ async def new_order( resp = await self._post(d.new_order, {"identifiers": identifiers}) data = resp.json() # - return ACMEOrder( + return AcmeOrder( authorizations=[ - ACMEAuthorization(domain=i["value"], url=a) + AcmeAuthorization(domain=i["value"], url=a) for i, a in zip(identifiers, data["authorizations"]) ], finalize=data["finalize"], ) async def get_challenges( - self: "ACMEClient", auth: ACMEAuthorization - ) -> List[ACMEChallenge]: + self: "AcmeClient", auth: AcmeAuthorization + ) -> List[AcmeChallenge]: """ Get a challenge for an authoriations. @@ -452,7 +452,7 @@ async def get_challenges( Examples: ``` python - async with ACMEClient( + async with AcmeClient( directory, key=key, account_url=account_url @@ -466,26 +466,26 @@ async def get_challenges( ``` Args: - auth: ACMEAuthorization object, usually from - ACMEOrder.authorizations. + auth: AcmeAuthorization object, usually from + AcmeOrder.authorizations. Returns: - List of ACMEChallenge. + List of AcmeChallenge. Raises: - ACMEError: In case of the errors. + AcmeError: In case of the errors. """ logger.warning("Getting challenges for %s", auth.domain) self._check_bound() resp = await self._post(auth.url, None) data = resp.json() return [ - ACMEChallenge(type=d["type"], url=d["url"], token=d["token"]) + AcmeChallenge(type=d["type"], url=d["url"], token=d["token"]) for d in data["challenges"] ] async def respond_challenge( - self: "ACMEClient", challenge: ACMEChallenge + self: "AcmeClient", challenge: AcmeChallenge ) -> None: """ Respond to challenge. @@ -503,7 +503,7 @@ async def respond_challenge( await self._post(challenge.url, {}) async def wait_for_authorization( - self: "ACMEClient", auth: ACMEAuthorization + self: "AcmeClient", auth: AcmeAuthorization ) -> None: """ Wait untill authorization became valid. @@ -526,7 +526,7 @@ async def wait_for_authorization( await self._random_delay(3.0) else: msg = f"Status is {status}" - raise ACMEAuthorizationError(msg) + raise AcmeAuthorizationError(msg) @staticmethod async def _random_delay(limit: float) -> None: @@ -569,17 +569,17 @@ def _get_order_status(resp: httpx.Response) -> str: Order status Raises: - ACMECertificateError: if status is invalid + AcmeCertificateError: if status is invalid """ data = resp.json() status = cast(str, data["status"]) if status == "invalid": msg = "Failed to finalize order" - raise ACMECertificateError(msg) + raise AcmeCertificateError(msg) return status async def finalize_and_wait( - self: "ACMEClient", order: ACMEOrder, *, csr: bytes + self: "AcmeClient", order: AcmeOrder, *, csr: bytes ) -> bytes: """ Send finalization request and wait for the certificate. @@ -592,7 +592,7 @@ async def finalize_and_wait( Signed certificate in PEM format. Raises: - ACMECertificateError: When server refuses + AcmeCertificateError: When server refuses to sign the certificate. """ logger.warning("Finalizing order") @@ -614,7 +614,7 @@ async def finalize_and_wait( resp = await self._post(data["certificate"], None) return resp.text.encode() - async def sign(self: "ACMEClient", domain: str, csr: bytes) -> bytes: + async def sign(self: "AcmeClient", domain: str, csr: bytes) -> bytes: """ Sign the CSR and get a certificate for domain. @@ -628,9 +628,9 @@ async def sign(self: "ACMEClient", domain: str, csr: bytes) -> bytes: Examples: ``` python - class SignClient(ACMEClient): + class SignClient(AcmeClient): async def fulfill_http_01( - self, domain: str, challenge: ACMEChallenge + self, domain: str, challenge: AcmeChallenge ) -> bool: # do something useful return True @@ -647,10 +647,10 @@ async def fulfill_http_01( The signed certificate in PEM format. Raises: - ACMETimeoutError: On timeouts. - ACMEFulfillmentFailed: If the client failed to + AcmeTimeoutError: On timeouts. + AcmeFulfillmentFailed: If the client failed to fulfill any challenge. - ACMEError: and subclasses in case of other errors. + AcmeError: and subclasses in case of other errors. """ logger.warning("Signing CSR for domain %s", domain) self._check_bound() @@ -668,7 +668,7 @@ async def fulfill_http_01( fulfilled_challenge = ch break else: - raise ACMEFulfillmentFailed + raise AcmeFulfillmentFailed # Wait for authorization became valid await self._wait_for(self.wait_for_authorization(auth), 60.0) # Clear challenge @@ -678,7 +678,7 @@ async def fulfill_http_01( self.finalize_and_wait(order, csr=csr), 60.0 ) - async def _head(self: "ACMEClient", url: str) -> httpx.Response: + async def _head(self: "AcmeClient", url: str) -> httpx.Response: """ Perform HTTP HEAD request. @@ -692,7 +692,7 @@ async def _head(self: "ACMEClient", url: str) -> httpx.Response: httpx.Response instance. Raises: - ACMEError: in case of error. + AcmeError: in case of error. """ logger.warning("HEAD %s", url) async with self._get_client() as client: @@ -702,13 +702,13 @@ async def _head(self: "ACMEClient", url: str) -> httpx.Response: self._timeout, ) except httpx.HTTPError as e: - raise ACMEConnectError from e + raise AcmeConnectError from e self._check_response(r) self._nonce_from_response(r) return r async def _post( - self: "ACMEClient", url: str, data: Optional[Dict[str, Any]] + self: "AcmeClient", url: str, data: Optional[Dict[str, Any]] ) -> httpx.Response: """ Perform HTTP POST request. @@ -716,7 +716,7 @@ async def _post( Performs HTTP POST request using underlied HTTP client. Updates nonces if any responded. Retries once if the nonce was rejected by server. - Raises an ACMEError in case of the error. + Raises an AcmeError in case of the error. Args: url: Request URL @@ -727,17 +727,17 @@ async def _post( httpx.Response instance. Raises: - ACMEError: in case of the error. + AcmeError: in case of the error. """ try: return await self._post_once(url, data) - except ACMEBadNonceError: + except AcmeBadNonceError: # Retry once logger.warning("POST retry on invalid nonce") return await self._post_once(url, data) async def _post_once( - self: "ACMEClient", url: str, data: Optional[Dict[str, Any]] + self: "AcmeClient", url: str, data: Optional[Dict[str, Any]] ) -> httpx.Response: """ Perform a single HTTP POST request. @@ -753,10 +753,10 @@ async def _post_once( httpx.Response instance. Raises: - ACMEConnectError: in case of transport errors. - ACMETimeoutError: on timeouts. - ACMEBadNonceError: in case of bad nonce. - ACMEError: in case of the error. + AcmeConnectError: in case of transport errors. + AcmeTimeoutError: on timeouts. + AcmeBadNonceError: in case of bad nonce. + AcmeError: in case of the error. """ logger.warning("POST %s", url) nonce = await self._get_nonce(url) @@ -774,12 +774,12 @@ async def _post_once( self._timeout, ) except httpx.HTTPError as e: - raise ACMEConnectError from e + raise AcmeConnectError from e self._check_response(resp) self._nonce_from_response(resp) return resp - async def _get_nonce(self: "ACMEClient", url: str) -> bytes: + async def _get_nonce(self: "AcmeClient", url: str) -> bytes: """ Request new nonce. @@ -801,7 +801,7 @@ async def _get_nonce(self: "ACMEClient", url: str) -> bytes: self._check_response(resp) return self._nonces.pop() - def _nonce_from_response(self: "ACMEClient", resp: httpx.Response) -> None: + def _nonce_from_response(self: "AcmeClient", resp: httpx.Response) -> None: """ Get nonce from response, if present. @@ -811,7 +811,7 @@ def _nonce_from_response(self: "ACMEClient", resp: httpx.Response) -> None: resp: HTTP Response Raises: - ACMEBadNonceError: on malformed nonce. + AcmeBadNonceError: on malformed nonce. """ nonce = resp.headers.get(self.NONCE_HEADER) if nonce is None: @@ -821,14 +821,14 @@ def _nonce_from_response(self: "ACMEClient", resp: httpx.Response) -> None: b_nonce = decode_b64jose(nonce) if b_nonce in self._nonces: msg = "Duplicated nonce" - raise ACMEError(msg) + raise AcmeError(msg) self._nonces.add(b_nonce) except DeserializationError as e: logger.error("Bad nonce: %s", e) - raise ACMEBadNonceError from e + raise AcmeBadNonceError from e def _to_jws( - self: "ACMEClient", + self: "AcmeClient", data: Optional[Dict[str, Any]], *, nonce: Optional[bytes], @@ -863,9 +863,9 @@ def _check_response(resp: httpx.Response) -> None: resp: Response instance Raises: - ACMEUndecodableError: When failed to decode an error. - ACMEBadNonceError: When the server rejects nonce. - ACMERateLimitError: When the server rejects the request + AcmeUndecodableError: When failed to decode an error. + AcmeBadNonceError: When the server rejects nonce. + AcmeRateLimitError: When the server rejects the request due to high request rate. """ if resp.status_code < BAD_REQUEST: @@ -873,18 +873,18 @@ def _check_response(resp: httpx.Response) -> None: try: jdata = resp.json() except ValueError as e: - raise ACMEUndecodableError from e + raise AcmeUndecodableError from e e_type = jdata.get("type", "") if e_type == "urn:ietf:params:acme:error:badNonce": - raise ACMEBadNonceError + raise AcmeBadNonceError if e_type == "urn:ietf:params:acme:error:rateLimited": - raise ACMERateLimitError + raise AcmeRateLimitError if e_type == "urn:ietf:params:acme:error:unauthorized": - raise ACMEUnauthorizedError + raise AcmeUnauthorizedError e_detail = jdata.get("detail", "") msg = f"[{resp.status_code}] {e_type} {e_detail}" logger.error("Response error: %s", msg) - raise ACMEError(msg) + raise AcmeError(msg) @staticmethod def get_key() -> JWKRSA: @@ -893,7 +893,7 @@ def get_key() -> JWKRSA: Examples: ``` python - key = ACMEClient.get_key() + key = AcmeClient.get_key() ``` Returns: @@ -907,7 +907,7 @@ def get_key() -> JWKRSA: return JWKRSA(key=private_key) async def fulfill_challenge( - self: "ACMEClient", domain: str, challenge: ACMEChallenge + self: "AcmeClient", domain: str, challenge: AcmeChallenge ) -> bool: """ Try to fulfill challege. @@ -917,7 +917,7 @@ async def fulfill_challenge( Args: domain: Domain name. - challenge: ACMEChallenge instance. + challenge: AcmeChallenge instance. Returns: True: if the challenge is fulfilled. @@ -942,19 +942,19 @@ async def fulfill_challenge( return r async def fulfill_tls_alpn_01( - self: "ACMEClient", domain: str, challenge: ACMEChallenge + self: "AcmeClient", domain: str, challenge: AcmeChallenge ) -> bool: """ Fulfill the `tls-alpn-01` type of challenge. Should be overriden in subclasses to perform all necessary jobs. Override - [clear_tls_alpn_01][gufo.acme.clients.base.ACMEClient.clear_tls_alpn_01] + [clear_tls_alpn_01][gufo.acme.clients.base.AcmeClient.clear_tls_alpn_01] to perform cleanup. Args: domain: Domain name. - challenge: ACMEChallenge instance. + challenge: AcmeChallenge instance. Returns: True: if the challenge is fulfilled. @@ -963,19 +963,19 @@ async def fulfill_tls_alpn_01( return False async def fulfill_http_01( - self: "ACMEClient", domain: str, challenge: ACMEChallenge + self: "AcmeClient", domain: str, challenge: AcmeChallenge ) -> bool: """ Fulfill the `http-01` type of challenge. Should be overriden in subclasses to perform all necessary jobs. Override - [clear_http_01][gufo.acme.clients.base.ACMEClient.clear_http_01] + [clear_http_01][gufo.acme.clients.base.AcmeClient.clear_http_01] to perform cleanup. Args: domain: Domain name. - challenge: ACMEChallenge instance. + challenge: AcmeChallenge instance. Returns: True: if the challenge is fulfilled. @@ -984,19 +984,19 @@ async def fulfill_http_01( return False async def fulfill_dns_01( - self: "ACMEClient", domain: str, challenge: ACMEChallenge + self: "AcmeClient", domain: str, challenge: AcmeChallenge ) -> bool: """ Fulfill the `dns-01` type of challenge. Should be overriden in subclasses to perform all necessary jobs. Override - [clear_dns_01][gufo.acme.clients.base.ACMEClient.clear_dns_01] + [clear_dns_01][gufo.acme.clients.base.AcmeClient.clear_dns_01] to perform cleanup. Args: domain: Domain name. - challenge: ACMEChallenge instance. + challenge: AcmeChallenge instance. Returns: True: if the challenge is fulfilled. @@ -1005,14 +1005,14 @@ async def fulfill_dns_01( return False async def clear_challenge( - self: "ACMEClient", domain: str, challenge: ACMEChallenge + self: "AcmeClient", domain: str, challenge: AcmeChallenge ) -> None: """ Clear up fulfillment after the challenge has been validated. Args: domain: Domain name. - challenge: ACMEChallenge instance. + challenge: AcmeChallenge instance. """ logger.warning( "Trying to clear challenge %s for %s", challenge.type, domain @@ -1026,7 +1026,7 @@ async def clear_challenge( return None async def clear_tls_alpn_01( - self: "ACMEClient", domain: str, challenge: ACMEChallenge + self: "AcmeClient", domain: str, challenge: AcmeChallenge ) -> None: """ Clear up fulfillment after the `tls-alpn-01` has been validated. @@ -1035,11 +1035,11 @@ async def clear_tls_alpn_01( Args: domain: Domain name. - challenge: ACMEChallenge instance. + challenge: AcmeChallenge instance. """ async def clear_http_01( - self: "ACMEClient", domain: str, challenge: ACMEChallenge + self: "AcmeClient", domain: str, challenge: AcmeChallenge ) -> None: """ Clear up fulfillment after the `http-01` has been validated. @@ -1048,11 +1048,11 @@ async def clear_http_01( Args: domain: Domain name. - challenge: ACMEChallenge instance. + challenge: AcmeChallenge instance. """ async def clear_dns_01( - self: "ACMEClient", domain: str, challenge: ACMEChallenge + self: "AcmeClient", domain: str, challenge: AcmeChallenge ) -> None: """ Clear up fulfillment after the `dns-01` has been validated. @@ -1061,11 +1061,11 @@ async def clear_dns_01( Args: domain: Domain name. - challenge: ACMEChallenge instance. + challenge: AcmeChallenge instance. """ def get_key_authorization( - self: "ACMEClient", challenge: ACMEChallenge + self: "AcmeClient", challenge: AcmeChallenge ) -> bytes: """ Calculate value for key authorization. @@ -1143,13 +1143,13 @@ def get_domain_csr(domain: str, private_key: bytes) -> bytes: # Convert CSR to PEM format return csr.public_bytes(encoding=Encoding.PEM) - def get_state(self: "ACMEClient") -> bytes: + def get_state(self: "AcmeClient") -> bytes: """ Serialize the state of client to a stream of bytes. The state will contain all necessasy information to instantiate the new client by the - `ACMEClient.from_state(...)` + `AcmeClient.from_state(...)` Return: State of the client as a stream of bytes @@ -1165,10 +1165,10 @@ def get_state(self: "ACMEClient") -> bytes: @classmethod def from_state(cls: Type[CT], state: bytes, **kwargs: Any) -> CT: """ - Restore ACMEClient from the state. + Restore AcmeClient from the state. Restore the state of client from result of - [ACMEClient.get_state][gufo.acme.clients.base.ACMEClient.get_state] + [AcmeClient.get_state][gufo.acme.clients.base.AcmeClient.get_state] call. Args: @@ -1176,7 +1176,7 @@ def from_state(cls: Type[CT], state: bytes, **kwargs: Any) -> CT: kwargs: An additional arguments to be passed to constructor. Returns: - New ACMEClient instance. + New AcmeClient instance. """ s = json.loads(state) return cls( diff --git a/src/gufo/acme/clients/dav.py b/src/gufo/acme/clients/dav.py index 1f3293d..a0ff21f 100644 --- a/src/gufo/acme/clients/dav.py +++ b/src/gufo/acme/clients/dav.py @@ -1,9 +1,9 @@ # --------------------------------------------------------------------- -# Gufo ACME: DAVACMEClient implementation +# Gufo ACME: DavAcmeClient implementation # --------------------------------------------------------------------- # Copyright (C) 2023, Gufo Labs # --------------------------------------------------------------------- -"""A DAVACMEClient implementation.""" +"""A DavAcmeClient implementation.""" # Python modules from typing import Any @@ -11,16 +11,16 @@ # Third-party modules import httpx -from ..error import ACMEFulfillmentFailed +from ..error import AcmeFulfillmentFailed # Gufo ACME modules -from ..types import ACMEChallenge -from .base import ACMEClient +from ..types import AcmeChallenge +from .base import AcmeClient HTTP_MAX_VALID = 299 -class DAVACMEClient(ACMEClient): +class DavAcmeClient(AcmeClient): """ WebDAV-compatible ACME Client. @@ -36,7 +36,7 @@ class DAVACMEClient(ACMEClient): """ def __init__( - self: "DAVACMEClient", + self: "DavAcmeClient", directory_url: str, *, username: str, @@ -47,7 +47,7 @@ def __init__( self.username = username self.password = password - def get_auth(self: "DAVACMEClient") -> httpx.Auth: + def get_auth(self: "DavAcmeClient") -> httpx.Auth: """ Get Auth for request. @@ -69,10 +69,10 @@ def _check_dav_response(resp: httpx.Response) -> None: """ if resp.status_code > HTTP_MAX_VALID: msg = f"Failed to put challenge: code {resp.status_code}" - raise ACMEFulfillmentFailed(msg) + raise AcmeFulfillmentFailed(msg) async def fulfill_http_01( - self: "DAVACMEClient", domain: str, challenge: ACMEChallenge + self: "DavAcmeClient", domain: str, challenge: AcmeChallenge ) -> bool: """ Perform http-01 fullfilment. @@ -81,13 +81,13 @@ async def fulfill_http_01( Args: domain: Domain name - challenge: ACMEChallenge instance, containing token. + challenge: AcmeChallenge instance, containing token. Returns: True - on succeess Raises: - ACMEFulfillmentFailed: On error. + AcmeFulfillmentFailed: On error. """ async with self._get_client() as client: v = self.get_key_authorization(challenge) @@ -100,17 +100,17 @@ async def fulfill_http_01( return True async def clear_http_01( - self: "DAVACMEClient", domain: str, challenge: ACMEChallenge + self: "DavAcmeClient", domain: str, challenge: AcmeChallenge ) -> None: """ Remove provisioned token. Args: domain: Domain name - challenge: ACMEChallenge instance, containing token. + challenge: AcmeChallenge instance, containing token. Raises: - ACMEFulfillmentFailed: On error. + AcmeFulfillmentFailed: On error. """ async with self._get_client() as client: resp = await client.delete( diff --git a/src/gufo/acme/clients/powerdns.py b/src/gufo/acme/clients/powerdns.py index a6d4745..6e71c61 100644 --- a/src/gufo/acme/clients/powerdns.py +++ b/src/gufo/acme/clients/powerdns.py @@ -14,17 +14,16 @@ # Third-party modules from josepy.json_util import encode_b64jose -from ..error import ACMEFulfillmentFailed - # Gufo ACME modules +from ..error import AcmeFulfillmentFailed from ..log import logger -from ..types import ACMEChallenge -from .base import ACMEClient +from ..types import AcmeChallenge +from .base import AcmeClient RESP_NO_CONTENT = 204 -class PowerDnsAcmeClient(ACMEClient): +class PowerDnsAcmeClient(AcmeClient): """ PowerDNS compatible ACME Client. @@ -59,10 +58,10 @@ def _check_api_response(resp: httpx.Response) -> None: if resp.status_code != RESP_NO_CONTENT: msg = f"Failed to fulfill: Server returned {resp}" logger.error(msg) - raise ACMEFulfillmentFailed(msg) + raise AcmeFulfillmentFailed(msg) async def fulfill_dns_01( - self: "PowerDnsAcmeClient", domain: str, challenge: ACMEChallenge + self: "PowerDnsAcmeClient", domain: str, challenge: AcmeChallenge ) -> bool: """ Fulfill dns-01 challenge. @@ -71,13 +70,13 @@ async def fulfill_dns_01( Args: domain: Domain name - challenge: ACMEChallenge instance, containing token. + challenge: AcmeChallenge instance, containing token. Returns: True - on succeess. Raises: - ACMEFulfillmentFailed: On error. + AcmeFulfillmentFailed: On error. """ # Calculate value v = encode_b64jose( diff --git a/src/gufo/acme/clients/web.py b/src/gufo/acme/clients/web.py index df82e07..d6a675f 100644 --- a/src/gufo/acme/clients/web.py +++ b/src/gufo/acme/clients/web.py @@ -1,9 +1,9 @@ # --------------------------------------------------------------------- -# Gufo ACME: WebACMEClient implementation +# Gufo ACME: WebAcmeClient implementation # --------------------------------------------------------------------- # Copyright (C) 2023, Gufo Labs # --------------------------------------------------------------------- -"""A WebACMEClient implementation.""" +"""A WebAcmeClient implementation.""" # Python modules import os @@ -12,11 +12,11 @@ # Gufo ACME modules from ..log import logger -from ..types import ACMEChallenge -from .base import ACMEClient +from ..types import AcmeChallenge +from .base import AcmeClient -class WebACMEClient(ACMEClient): +class WebAcmeClient(AcmeClient): """ A webserver-backed ACME client. @@ -30,7 +30,7 @@ class WebACMEClient(ACMEClient): """ def __init__( - self: "WebACMEClient", + self: "WebAcmeClient", directory_url: str, *, path: Union[str, Path], @@ -39,7 +39,7 @@ def __init__( super().__init__(directory_url, **kwargs) self.path = Path(path) - def _get_path(self: "WebACMEClient", challenge: ACMEChallenge) -> Path: + def _get_path(self: "WebAcmeClient", challenge: AcmeChallenge) -> Path: """ Get Path for challenge. @@ -52,7 +52,7 @@ def _get_path(self: "WebACMEClient", challenge: ACMEChallenge) -> Path: return self.path / Path(challenge.token) async def fulfill_http_01( - self: "WebACMEClient", domain: str, challenge: ACMEChallenge + self: "WebAcmeClient", domain: str, challenge: AcmeChallenge ) -> bool: """ Perform http-01 fullfilment. @@ -61,13 +61,13 @@ async def fulfill_http_01( Args: domain: Domain name - challenge: ACMEChallenge instance, containing token. + challenge: AcmeChallenge instance, containing token. Returns: True - on succeess Raises: - ACMEFulfillmentFailed: On error. + AcmeFulfillmentFailed: On error. """ path = self._get_path(challenge) v = self.get_key_authorization(challenge) @@ -77,17 +77,17 @@ async def fulfill_http_01( return True async def clear_http_01( - self: "WebACMEClient", domain: str, challenge: ACMEChallenge + self: "WebAcmeClient", domain: str, challenge: AcmeChallenge ) -> None: """ Remove provisioned token. Args: domain: Domain name - challenge: ACMEChallenge instance, containing token. + challenge: AcmeChallenge instance, containing token. Raises: - ACMEFulfillmentFailed: On error. + AcmeFulfillmentFailed: On error. """ path = self._get_path(challenge) if os.path.exists(path): diff --git a/src/gufo/acme/error.py b/src/gufo/acme/error.py index a8ce689..0f842a2 100644 --- a/src/gufo/acme/error.py +++ b/src/gufo/acme/error.py @@ -4,52 +4,52 @@ # Copyright (C) 2023, Gufo Labs # --------------------------------------------------------------------- -"""ACMEClient error classes.""" +"""AcmeClient error classes.""" -class ACMEError(Exception): +class AcmeError(Exception): """Base class for all Gufo Acme errors.""" -class ACMEBadNonceError(ACMEError): +class AcmeBadNonceError(AcmeError): """Server rejects a nounce as invalid.""" -class ACMETimeoutError(ACMEError): +class AcmeTimeoutError(AcmeError): """Operation timed out.""" -class ACMEConnectError(ACMEError): +class AcmeConnectError(AcmeError): """Failed to connect ACME server.""" -class ACMERateLimitError(ACMEError): +class AcmeRateLimitError(AcmeError): """Request rate limit exceeded.""" -class ACMEAlreadyRegistered(ACMEError): +class AcmeAlreadyRegistered(AcmeError): """Client is alredy registered.""" -class ACMEUndecodableError(ACMEError): +class AcmeUndecodableError(AcmeError): """Cannot decode an error message.""" -class ACMEAuthorizationError(ACMEError): +class AcmeAuthorizationError(AcmeError): """Failed to pass an authorization.""" -class ACMEFulfillmentFailed(ACMEError): +class AcmeFulfillmentFailed(AcmeError): """Failed to fulfill challenge.""" -class ACMENotRegistredError(ACMEError): +class AcmeNotRegistredError(AcmeError): """Client is not registred.""" -class ACMEUnauthorizedError(ACMEError): +class AcmeUnauthorizedError(AcmeError): """Request is not authorized.""" -class ACMECertificateError(ACMEError): +class AcmeCertificateError(AcmeError): """Failed to finalize.""" diff --git a/src/gufo/acme/types.py b/src/gufo/acme/types.py index 4968b03..02339bb 100644 --- a/src/gufo/acme/types.py +++ b/src/gufo/acme/types.py @@ -12,7 +12,7 @@ @dataclass -class ACMEAuthorization(object): +class AcmeAuthorization(object): """ ACME Authorization resource. @@ -26,7 +26,7 @@ class ACMEAuthorization(object): @dataclass -class ACMEOrder(object): +class AcmeOrder(object): """ ACME order resource. @@ -35,12 +35,12 @@ class ACMEOrder(object): finalize: URL to finalize the order. """ - authorizations: List[ACMEAuthorization] + authorizations: List[AcmeAuthorization] finalize: str @dataclass -class ACMEChallenge(object): +class AcmeChallenge(object): """ ACME challenge resource. @@ -56,7 +56,7 @@ class ACMEChallenge(object): @dataclass -class ACMEDirectory(object): +class AcmeDirectory(object): """ ACME directory. diff --git a/tests/clients/test_base.py b/tests/clients/test_base.py index 8e438e5..ce764e4 100644 --- a/tests/clients/test_base.py +++ b/tests/clients/test_base.py @@ -17,21 +17,21 @@ import pytest # Gufo ACME modules -from gufo.acme.clients.base import ACMEClient +from gufo.acme.clients.base import AcmeClient from gufo.acme.error import ( - ACMEAlreadyRegistered, - ACMEBadNonceError, - ACMECertificateError, - ACMEConnectError, - ACMEError, - ACMEFulfillmentFailed, - ACMENotRegistredError, - ACMERateLimitError, - ACMETimeoutError, - ACMEUnauthorizedError, - ACMEUndecodableError, + AcmeAlreadyRegistered, + AcmeBadNonceError, + AcmeCertificateError, + AcmeConnectError, + AcmeError, + AcmeFulfillmentFailed, + AcmeNotRegistredError, + AcmeRateLimitError, + AcmeTimeoutError, + AcmeUnauthorizedError, + AcmeUndecodableError, ) -from gufo.acme.types import ACMEChallenge +from gufo.acme.types import AcmeChallenge from httpx import ConnectError, Response from josepy.jwk import JWKRSA @@ -42,7 +42,7 @@ def test_get_directory() -> None: async def inner(): - async with ACMEClient(DIRECTORY, key=KEY) as client: + async with AcmeClient(DIRECTORY, key=KEY) as client: d1 = await client._get_directory() assert d1.new_account # Cached @@ -54,7 +54,7 @@ async def inner(): def test_get_nonce() -> None: async def inner(): - async with ACMEClient(DIRECTORY, key=KEY) as client: + async with AcmeClient(DIRECTORY, key=KEY) as client: nonce = await client._get_nonce(DIRECTORY) assert nonce assert isinstance(nonce, bytes) @@ -63,7 +63,7 @@ async def inner(): def test_to_jws() -> None: - client = ACMEClient(DIRECTORY, key=KEY) + client = AcmeClient(DIRECTORY, key=KEY) nonce = b"12345" msg = client._to_jws( { @@ -86,23 +86,23 @@ def test_to_jws() -> None: def test_check_unbound(): - client = ACMEClient(DIRECTORY, key=KEY) + client = AcmeClient(DIRECTORY, key=KEY) client._check_unbound() - with pytest.raises(ACMENotRegistredError): + with pytest.raises(AcmeNotRegistredError): client._check_bound() def test_check_bound(): - client = ACMEClient(DIRECTORY, key=KEY, account_url="http://127.0.0.1/acc") + client = AcmeClient(DIRECTORY, key=KEY, account_url="http://127.0.0.1/acc") client._check_bound() - with pytest.raises(ACMEAlreadyRegistered): + with pytest.raises(AcmeAlreadyRegistered): client._check_unbound() def test_new_and_deactivate_account() -> None: async def inner(): - key = ACMEClient.get_key() - async with ACMEClient(DIRECTORY, key=key) as client: + key = AcmeClient.get_key() + async with AcmeClient(DIRECTORY, key=key) as client: client._check_unbound() uri = await client.new_account(EMAIL) assert uri is not None @@ -116,17 +116,17 @@ async def inner(): def test_get_public_key() -> None: - key = ACMEClient.get_key() + key = AcmeClient.get_key() assert key assert isinstance(key, JWKRSA) def test_already_registered() -> None: async def inner(): - async with ACMEClient( + async with AcmeClient( DIRECTORY, key=KEY, account_url="http://127.0.0.1/" ) as client: - with pytest.raises(ACMEAlreadyRegistered): + with pytest.raises(AcmeAlreadyRegistered): await client.new_account(EMAIL) asyncio.run(inner()) @@ -179,27 +179,27 @@ async def post(self, url, *args, **kwargs: Dict[str, Any]): await self._blackhole() -class BlackholeACMEClient(ACMEClient): +class BlackholeAcmeClient(AcmeClient): DEFAULT_TIMEOUT = 0.0001 def _get_client(self) -> BlackholeHttpClient: return BlackholeHttpClient() -class BlackholeACMEClientBadNonce(BlackholeACMEClient): +class BlackholeAcmeClientBadNonce(BlackholeAcmeClient): async def _post_once(self, url: str, data: Dict[str, Any]) -> Response: - raise ACMEBadNonceError() + raise AcmeBadNonceError() -class BuggyACMEClient(ACMEClient): +class BuggyAcmeClient(AcmeClient): def _get_client(self) -> BuggyHttpClient: return BuggyHttpClient() def test_get_directory_timeout(): async def inner(): - async with BlackholeACMEClient(DIRECTORY, key=KEY) as client: - with pytest.raises(ACMETimeoutError): + async with BlackholeAcmeClient(DIRECTORY, key=KEY) as client: + with pytest.raises(AcmeTimeoutError): await client._get_directory() asyncio.run(inner()) @@ -207,8 +207,8 @@ async def inner(): def test_get_directory_error(): async def inner(): - async with BuggyACMEClient(DIRECTORY, key=KEY) as client: - with pytest.raises(ACMEConnectError): + async with BuggyAcmeClient(DIRECTORY, key=KEY) as client: + with pytest.raises(AcmeConnectError): await client._get_directory() asyncio.run(inner()) @@ -216,8 +216,8 @@ async def inner(): def test_head_timeout(): async def inner(): - async with BlackholeACMEClient(DIRECTORY, key=KEY) as client: - with pytest.raises(ACMETimeoutError): + async with BlackholeAcmeClient(DIRECTORY, key=KEY) as client: + with pytest.raises(AcmeTimeoutError): await client._head("") asyncio.run(inner()) @@ -225,8 +225,8 @@ async def inner(): def test_head_error(): async def inner(): - async with BuggyACMEClient(DIRECTORY, key=KEY) as client: - with pytest.raises(ACMEConnectError): + async with BuggyAcmeClient(DIRECTORY, key=KEY) as client: + with pytest.raises(AcmeConnectError): await client._head("") asyncio.run(inner()) @@ -234,12 +234,12 @@ async def inner(): def test_post_timeout(): async def inner(): - async with BlackholeACMEClient(DIRECTORY, key=KEY) as client: + async with BlackholeAcmeClient(DIRECTORY, key=KEY) as client: # Avoid HTTP call in get_nonce client._nonces.add( b"\xa0[\xe7\x94S\xf5\xc0\x88Q\x95\x84\xb6\x8d6\x97l" ) - with pytest.raises(ACMETimeoutError): + with pytest.raises(AcmeTimeoutError): await client._post("", {}) asyncio.run(inner()) @@ -247,12 +247,12 @@ async def inner(): def test_post_error(): async def inner(): - async with BuggyACMEClient(DIRECTORY, key=KEY) as client: + async with BuggyAcmeClient(DIRECTORY, key=KEY) as client: # Avoid HTTP call in get_nonce client._nonces.add( b"\xa0[\xe7\x94S\xf5\xc0\x88Q\x95\x84\xb6\x8d6\x97l" ) - with pytest.raises(ACMEConnectError): + with pytest.raises(AcmeConnectError): await client._post("", {}) asyncio.run(inner()) @@ -260,12 +260,12 @@ async def inner(): def test_post_retry(): async def inner(): - async with BlackholeACMEClientBadNonce(DIRECTORY, key=KEY) as client: + async with BlackholeAcmeClientBadNonce(DIRECTORY, key=KEY) as client: # Avoid HTTP call in get_nonce client._nonces.add( b"\xa0[\xe7\x94S\xf5\xc0\x88Q\x95\x84\xb6\x8d6\x97l" ) - with pytest.raises(ACMEBadNonceError): + with pytest.raises(AcmeBadNonceError): await client._post("", {}) asyncio.run(inner()) @@ -284,7 +284,7 @@ async def inner(): def test_email_to_contacts( email: Union[str, List[str]], expected: List[str] ) -> None: - client = ACMEClient(DIRECTORY, key=KEY) + client = AcmeClient(DIRECTORY, key=KEY) r = client._email_to_contacts(email) assert r == expected @@ -305,43 +305,43 @@ def test_email_to_contacts( def test_domain_to_identifiers( domain: Union[str, List[str]], expected: List[str] ) -> None: - client = ACMEClient(DIRECTORY, key=KEY) + client = AcmeClient(DIRECTORY, key=KEY) r = client._domain_to_identifiers(domain) assert r == expected def test_check_response_err_no_json() -> None: resp = Response(400, text="foobar") - with pytest.raises(ACMEUndecodableError): - ACMEClient._check_response(resp) + with pytest.raises(AcmeUndecodableError): + AcmeClient._check_response(resp) @pytest.mark.parametrize( ("j", "etype"), [ - ({"type": "urn:ietf:params:acme:error:badNonce"}, ACMEBadNonceError), + ({"type": "urn:ietf:params:acme:error:badNonce"}, AcmeBadNonceError), ( {"type": "urn:ietf:params:acme:error:rateLimited"}, - ACMERateLimitError, + AcmeRateLimitError, ), ( {"type": "urn:ietf:params:acme:error:badSignatureAlgorithm"}, - ACMEError, + AcmeError, ), ( {"type": "urn:ietf:params:acme:error:unauthorized"}, - ACMEUnauthorizedError, + AcmeUnauthorizedError, ), ], ) def test_check_response_err(j, etype): resp = Response(400, json=j) with pytest.raises(etype): - ACMEClient._check_response(resp) + AcmeClient._check_response(resp) def test_nonce_from_response(): - client = ACMEClient(DIRECTORY, key=KEY) + client = AcmeClient(DIRECTORY, key=KEY) assert not client._nonces resp = Response(200, headers={"Replay-Nonce": "oFvnlFP1wIhRlYS2jTaXbA"}) client._nonce_from_response(resp) @@ -351,7 +351,7 @@ def test_nonce_from_response(): def test_nonce_from_response_none(): - client = ACMEClient(DIRECTORY, key=KEY) + client = AcmeClient(DIRECTORY, key=KEY) assert not client._nonces resp = Response(200) client._nonce_from_response(resp) @@ -359,19 +359,19 @@ def test_nonce_from_response_none(): def test_nonce_from_response_decode_error(): - client = ACMEClient(DIRECTORY, key=KEY) + client = AcmeClient(DIRECTORY, key=KEY) assert not client._nonces resp = Response(200, headers={"Replay-Nonce": "x"}) - with pytest.raises(ACMEBadNonceError): + with pytest.raises(AcmeBadNonceError): client._nonce_from_response(resp) def test_nonce_from_response_duplicated(): - client = ACMEClient(DIRECTORY, key=KEY) + client = AcmeClient(DIRECTORY, key=KEY) assert not client._nonces resp = Response(200, headers={"Replay-Nonce": "oFvnlFP1wIhRlYS2jTaXbA"}) client._nonce_from_response(resp) - with pytest.raises(ACMEError): + with pytest.raises(AcmeError): client._nonce_from_response(resp) @@ -427,7 +427,7 @@ def test_nonce_from_response_duplicated(): def test_pem_to_ber(): - der = ACMEClient._pem_to_der(TEST_CSR_PEM) + der = AcmeClient._pem_to_der(TEST_CSR_PEM) expected = base64.b64decode(TEST_CSR_DER) assert der == expected @@ -440,13 +440,13 @@ def test_sign_no_fulfilment(): async def inner(): csr_pem = get_csr_pem(domain) # - pk = ACMEClient.get_key() - async with ACMEClient(DIRECTORY, key=pk) as client: + pk = AcmeClient.get_key() + async with AcmeClient(DIRECTORY, key=pk) as client: # Register account uri = await client.new_account(EMAIL) assert uri # Create new order - with pytest.raises(ACMEFulfillmentFailed): + with pytest.raises(AcmeFulfillmentFailed): await client.sign(domain, csr_pem) # Deactivate account await client.deactivate_account() @@ -459,8 +459,8 @@ async def inner(): "ch_type", ["http-01", "dns-01", "tls-alpn-01", "invalid"] ) def test_default_fulfilment(ch_type: str) -> None: - chall = ACMEChallenge(type=ch_type, url="", token="") - client = ACMEClient(DIRECTORY, key=KEY) + chall = AcmeChallenge(type=ch_type, url="", token="") + client = AcmeClient(DIRECTORY, key=KEY) r = asyncio.run(client.fulfill_challenge("example.com", chall)) assert r is False @@ -469,24 +469,24 @@ def test_default_fulfilment(ch_type: str) -> None: "ch_type", ["http-01", "dns-01", "tls-alpn-01", "invalid"] ) def test_default_clear(ch_type: str) -> None: - chall = ACMEChallenge(type=ch_type, url="", token="") - client = ACMEClient(DIRECTORY, key=KEY) + chall = AcmeChallenge(type=ch_type, url="", token="") + client = AcmeClient(DIRECTORY, key=KEY) asyncio.run(client.clear_challenge("example.com", chall)) def test_get_csr() -> None: - private_key = ACMEClient.get_domain_private_key() + private_key = AcmeClient.get_domain_private_key() assert b"BEGIN RSA PRIVATE KEY" in private_key assert b"END RSA PRIVATE KEY" in private_key - csr = ACMEClient.get_domain_csr("example.com", private_key) + csr = AcmeClient.get_domain_csr("example.com", private_key) assert b"BEGIN CERTIFICATE REQUEST" in csr assert b"END CERTIFICATE REQUEST" in csr def test_state1() -> None: - client = ACMEClient(DIRECTORY, key=KEY) + client = AcmeClient(DIRECTORY, key=KEY) state = client.get_state() - client2 = ACMEClient.from_state(state) + client2 = AcmeClient.from_state(state) assert client is not client2 assert client._directory == client2._directory assert client._key == client2._key @@ -494,11 +494,11 @@ def test_state1() -> None: def test_state2() -> None: - client = ACMEClient( + client = AcmeClient( DIRECTORY, key=KEY, account_url="https://127.0.0.1/acc" ) state = client.get_state() - client2 = ACMEClient.from_state(state) + client2 = AcmeClient.from_state(state) assert client is not client2 assert client._directory == client2._directory assert client._key == client2._key @@ -507,11 +507,11 @@ def test_state2() -> None: def test_invalid_order_status() -> None: resp = httpx.Response(200, json={"status": "invalid"}) - with pytest.raises(ACMECertificateError): - ACMEClient._get_order_status(resp) + with pytest.raises(AcmeCertificateError): + AcmeClient._get_order_status(resp) def test_valid_order_status() -> None: resp = httpx.Response(200, json={"status": "valid"}) - s = ACMEClient._get_order_status(resp) + s = AcmeClient._get_order_status(resp) assert s == "valid" diff --git a/tests/clients/test_dav.py b/tests/clients/test_dav.py index b55a84c..d9362a9 100644 --- a/tests/clients/test_dav.py +++ b/tests/clients/test_dav.py @@ -1,5 +1,5 @@ # --------------------------------------------------------------------- -# CSR Proxy: DAVACMEClient client tests +# CSR Proxy: DavAcmeClient client tests # --------------------------------------------------------------------- # Copyright (C) 2023, Gufo Labs # --------------------------------------------------------------------- @@ -14,9 +14,9 @@ import pytest # Gufo ACME modules -from gufo.acme.clients.base import ACMEClient -from gufo.acme.clients.dav import DAVACMEClient -from gufo.acme.error import ACMEFulfillmentFailed +from gufo.acme.clients.base import AcmeClient +from gufo.acme.clients.dav import DavAcmeClient +from gufo.acme.error import AcmeFulfillmentFailed from .utils import DIRECTORY, EMAIL, get_csr_pem, not_set, not_set_reason @@ -35,8 +35,8 @@ def test_sign(): async def inner(): csr_pem = get_csr_pem(domain) # - pk = DAVACMEClient.get_key() - async with DAVACMEClient( + pk = DavAcmeClient.get_key() + async with DavAcmeClient( DIRECTORY, username=os.getenv(ENV_CI_DAV_TEST_USER), password=os.getenv(ENV_CI_DAV_TEST_PASSWORD), @@ -58,16 +58,16 @@ async def inner(): def test_state(): - client = ACMEClient( + client = AcmeClient( DIRECTORY, - key=DAVACMEClient.get_key(), + key=DavAcmeClient.get_key(), ) state = client.get_state() - client2 = DAVACMEClient.from_state(state, username="user", password="pass") - assert isinstance(client2, DAVACMEClient) + client2 = DavAcmeClient.from_state(state, username="user", password="pass") + assert isinstance(client2, DavAcmeClient) def test_invalid_response(): resp = httpx.Response(400) - with pytest.raises(ACMEFulfillmentFailed): - DAVACMEClient._check_dav_response(resp) + with pytest.raises(AcmeFulfillmentFailed): + DavAcmeClient._check_dav_response(resp) diff --git a/tests/clients/test_powerdns.py b/tests/clients/test_powerdns.py index eb451d4..17fd98f 100644 --- a/tests/clients/test_powerdns.py +++ b/tests/clients/test_powerdns.py @@ -13,9 +13,9 @@ import pytest # Gufo ACME modules -from gufo.acme.clients.base import ACMEClient +from gufo.acme.clients.base import AcmeClient from gufo.acme.clients.powerdns import PowerDnsAcmeClient -from gufo.acme.error import ACMEFulfillmentFailed +from gufo.acme.error import AcmeFulfillmentFailed from .utils import DIRECTORY, EMAIL, get_csr_pem, not_set, not_set_reason @@ -57,7 +57,7 @@ async def inner(): def test_state(): - client = ACMEClient( + client = AcmeClient( DIRECTORY, key=PowerDnsAcmeClient.get_key(), ) @@ -82,5 +82,5 @@ def test_normalize_url(url: str, expected: str) -> None: def test_invalid_response(): resp = httpx.Response(200) - with pytest.raises(ACMEFulfillmentFailed): + with pytest.raises(AcmeFulfillmentFailed): PowerDnsAcmeClient._check_api_response(resp) diff --git a/tests/clients/test_web.py b/tests/clients/test_web.py index e416aa0..7edb354 100644 --- a/tests/clients/test_web.py +++ b/tests/clients/test_web.py @@ -4,9 +4,9 @@ import tempfile from pathlib import Path -# Gufo ACMEE modules -from gufo.acme.clients.web import WebACMEClient -from gufo.acme.types import ACMEChallenge +# Gufo AcmeE modules +from gufo.acme.clients.web import WebAcmeClient +from gufo.acme.types import AcmeChallenge from .utils import DIRECTORY, KEY @@ -19,14 +19,14 @@ def test_web() -> None: async def inner(): - chall = ACMEChallenge( + chall = AcmeChallenge( type="http-01", url="xxx", token="qPsDhTbvumL6q2DryepIyzJS1nMkghS92IqI4mug-7c", ) with tempfile.TemporaryDirectory() as tmp: path = Path(tmp) - client = WebACMEClient(DIRECTORY, key=KEY, path=path) + client = WebAcmeClient(DIRECTORY, key=KEY, path=path) r = await client.fulfill_http_01(DOMAIN, chall) assert r is True with open(path / chall.token, "rb") as fp: diff --git a/tests/clients/utils.py b/tests/clients/utils.py index b8ee6d5..f0a925a 100644 --- a/tests/clients/utils.py +++ b/tests/clients/utils.py @@ -9,7 +9,7 @@ from typing import Iterable # Gufo ACME modules -from gufo.acme.clients.base import ACMEClient +from gufo.acme.clients.base import AcmeClient from josepy.jwk import JWKRSA EMAIL = "acme-000000000@gufolabs.com" @@ -58,5 +58,5 @@ def not_set_reason(vars: Iterable[str]) -> str: def get_csr_pem(domain: str) -> bytes: """Generate CSR for domain in PEM format.""" - private_key = ACMEClient.get_domain_private_key() - return ACMEClient.get_domain_csr(domain, private_key) + private_key = AcmeClient.get_domain_private_key() + return AcmeClient.get_domain_csr(domain, private_key)