From b8c4a114f6a42324963c07132c28aeb1d38e7882 Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Thu, 14 Jan 2021 16:08:24 +0100 Subject: [PATCH] Add bearer validation --- cloudflare-ddns/__main__.py | 36 ++++++++++++++++++++---------------- cloudflare-ddns/constants.py | 7 +++++++ cloudflare-ddns/utils.py | 28 ++++++++++++++++++++++++++-- requirements.txt | 1 - 4 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 cloudflare-ddns/constants.py diff --git a/cloudflare-ddns/__main__.py b/cloudflare-ddns/__main__.py index 5311fc8..ab4033a 100644 --- a/cloudflare-ddns/__main__.py +++ b/cloudflare-ddns/__main__.py @@ -1,39 +1,43 @@ import logging import click -from email_validator import EmailNotValidError, validate_email +from .constants import DEFAULT_DELAY from .utils import parse_duration, validate_bearer -DEFAULT_DELAY = "5 minutes" +log = logging.getLogger("ddns") @click.command() @click.option('--delay', '-d', default=DEFAULT_DELAY, show_default=True) -@click.option('--email', '-u', prompt="Enter your Cloudflare Email address") -@click.option('--key', '-k', prompt="Enter your Cloudflare Auth key", hide_input=True) -def start(delay: str, email: str, key: str) -> None: +@click.option('--token', '-k', prompt="Enter your Cloudflare Token", hide_input=True, show_envvar=True) +@click.option('-v', '--verbose', is_flag=True, default=False) +def start(delay: str, token: str, verbose: int) -> None: """Main application entrypoint.""" + logging.basicConfig( + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + level=logging.DEBUG if verbose else logging.INFO + ) + try: duration = parse_duration(delay) except ValueError as e: - logging.error(f"Failed to parse delay: {e}") - logging.error("Exiting with code 64.") + log.error(f"Failed to parse delay: {e}") + log.error("Exiting with code 64.") exit(64) try: - validate_email(email) - except EmailNotValidError: - logging.warning(f"The email address {email} don't seem valid. Do you have a typo?") + log.debug("Validating bearer token.") - try: - validate_bearer(key) - except ...: - ... + validate_bearer(token) + except ValueError as e: + log.error(f"Failed to valid bearer token: {e}") + log.error("Exiting with code 64.") + exit(64) + else: + log.info("Successfully validated the bearer token.") # Main entrypoint if __name__ == "__main__": - logging.basicConfig(format="%(asctime)s - %(levelname)s - %(message)s") - start(auto_envvar_prefix="CF_DDNS") diff --git a/cloudflare-ddns/constants.py b/cloudflare-ddns/constants.py new file mode 100644 index 0000000..03d2fcd --- /dev/null +++ b/cloudflare-ddns/constants.py @@ -0,0 +1,7 @@ +# App defaults +DEFAULT_DELAY = "5 minutes" + +# Endpoints +BASE_ENDPOINT = "https://api.cloudflare.com/client/v4/" + +VERIFY_TOKEN = BASE_ENDPOINT + "user/tokens/verify" diff --git a/cloudflare-ddns/utils.py b/cloudflare-ddns/utils.py index a34a46b..487f48e 100644 --- a/cloudflare-ddns/utils.py +++ b/cloudflare-ddns/utils.py @@ -1,5 +1,11 @@ import re +import requests +from requests import Request +from requests.auth import AuthBase + +from .constants import VERIFY_TOKEN + DURATION_REGEX = re.compile( r"((?P\d+?) ?(days|day|D|d) ?)?" r"((?P\d+?) ?(hours|hour|H|h) ?)?" @@ -37,5 +43,23 @@ def parse_duration(duration: str) -> int: return duration -def validate_bearer(bearer: str) -> None: - ... +class BearerAuth(AuthBase): + """Bearer based authentication.""" + + def __init__(self, token: str) -> None: + self.token = token + + def __call__(self, r: Request) -> Request: + """Attach the Authorization header to the request.""" + r.headers["Authorization"] = f"Bearer {self.token}" + return r + + +def validate_bearer(token: str) -> None: + """Utility method to validate a CF bearer token.""" + bearer = BearerAuth(token) + r = requests.get(VERIFY_TOKEN, auth=bearer) + + if not r.json()["success"]: + error_message = ' / '.join(error["message"] for error in r.json()["errors"]) + raise ValueError(error_message) diff --git a/requirements.txt b/requirements.txt index 70a9287..0ef32ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ requests~=2.25.1 click8~=8.0.1 -email-validator~=1.1.2