Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .env.default
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ DESECSTACK_API_EMAIL_HOST_PASSWORD=
DESECSTACK_API_EMAIL_PORT=
DESECSTACK_API_SECRETKEY=
DESECSTACK_API_PSL_RESOLVER=
DESECSTACK_API_PCH_API=
DESECSTACK_API_PCH_API_TOKEN=
DESECSTACK_DBAPI_PASSWORD_desec=
DESECSTACK_MINIMUM_TTL_DEFAULT=900

Expand Down
2 changes: 0 additions & 2 deletions .env.dev.template
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ DESECSTACK_API_EMAIL_HOST_PASSWORD=
DESECSTACK_API_EMAIL_PORT=
DESECSTACK_API_SECRETKEY=insecure
DESECSTACK_API_PSL_RESOLVER=9.9.9.9
DESECSTACK_API_PCH_API=https://localhost/pch/api
DESECSTACK_API_PCH_API_TOKEN=insecure
DESECSTACK_DBAPI_PASSWORD_desec=insecure
DESECSTACK_MINIMUM_TTL_DEFAULT=900

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ env:
DESECSTACK_API_EMAIL_HOST_PASSWORD: password
DESECSTACK_API_EMAIL_PORT: 25
DESECSTACK_API_SECRETKEY: 9Fn33T5yGuds
DESECSTACK_API_PCH_API: http://pch
DESECSTACK_API_PCH_API_TOKEN: insecure
DESECSTACK_API_PSL_RESOLVER: 8.8.8.8
DESECSTACK_DBAPI_PASSWORD_desec: 9Fn33T5yGueeee
DESECSTACK_NSLORD_APIKEY: 9Fn33T5yGukjekwjew
Expand All @@ -50,9 +48,11 @@ jobs:
steps:
- uses: actions/checkout@v7
- name: Install Ruff
run: python3 -m pip install ruff
run: python3 -m pip install ruff==0.16.5
- name: Test desecapi formatting
run: ruff format --check api/
- name: Lint desecapi
run: ruff check api/

test-webapp:
# runs webapp unit tests
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ While there are certainly many ways to get started hacking desec-stack, here is

1. For PyCharm's Python Console, the environment variables of your `.env` file and `DJANGO_SETTINGS_MODULE=api.settings_quick_test` need to be configured in Settings › Build, Execution, Deployment › Console › Django Console. (Note that if you need to work with the database, you need to initialize it first by running all migrations; otherwise, the model tables will be missing from the database.)

1. **Code quality.** We use [Ruff](https://docs.astral.sh/ruff/) to ensure formatting consistency and minimal diffs. Before you commit Python code into the `api/` directory, please run `ruff format api/desecapi/`.
1. **Code quality.** We use [Ruff](https://docs.astral.sh/ruff/) to ensure formatting consistency and minimal diffs. Before you commit Python code into the `api/` directory, please run `ruff format api/` and `ruff check api/`. The lint rules are configured in `api/ruff.toml`.


## Debugging
Expand Down
2 changes: 1 addition & 1 deletion api/api/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def format(self, record):

@app.task(bind=True)
def debug_task(self):
print("Request: {0!r}".format(self.request))
print(f"Request: {self.request!r}")


@task_failure.connect()
Expand Down
16 changes: 4 additions & 12 deletions api/api/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@
Django settings for desecapi project.
"""

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
from datetime import timedelta

from django.conf.global_settings import PASSWORD_HASHERS as DEFAULT_PASSWORD_HASHERS

BASE_DIR = os.path.dirname(os.path.dirname(__file__))


# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ["DESECSTACK_API_SECRETKEY"]

Expand All @@ -21,9 +17,9 @@

ALLOWED_HOSTS = [
"api",
"desec.%s" % os.environ["DESECSTACK_DOMAIN"],
"update.dedyn.%s" % os.environ["DESECSTACK_DOMAIN"],
"update6.dedyn.%s" % os.environ["DESECSTACK_DOMAIN"],
"desec.{}".format(os.environ["DESECSTACK_DOMAIN"]),
"update.dedyn.{}".format(os.environ["DESECSTACK_DOMAIN"]),
"update6.dedyn.{}".format(os.environ["DESECSTACK_DOMAIN"]),
]

DEFAULT_EXCEPTION_REPORTER = "desecapi.debug.PayloadExceptionReporter"
Expand Down Expand Up @@ -168,7 +164,7 @@

# Public Suffix settings
PSL_RESOLVER = os.environ.get("DESECSTACK_API_PSL_RESOLVER")
LOCAL_PUBLIC_SUFFIXES = {"dedyn.%s" % os.environ["DESECSTACK_DOMAIN"]}
LOCAL_PUBLIC_SUFFIXES = {"dedyn.{}".format(os.environ["DESECSTACK_DOMAIN"])}

# PowerDNS-related
NSLORD_PDNS_API = "http://nslord:8081/api/v1/servers/localhost"
Expand Down Expand Up @@ -224,10 +220,6 @@
# Watchdog
WATCHDOG_SECONDARIES = os.environ.get("DESECSTACK_WATCHDOG_SECONDARIES", "").split()

# PCH
PCH_API = os.environ.get("DESECSTACK_API_PCH_API", "")
PCH_API_TOKEN = os.environ.get("DESECSTACK_API_PCH_API_TOKEN", "")

# Prometheus (see https://github.com/korfuri/django-prometheus/blob/master/documentation/exports.md)
# TODO Switch to PROMETHEUS_METRICS_EXPORT_PORT_RANGE instead of this workaround, which currently necessary to due
# https://github.com/korfuri/django-prometheus/issues/215
Expand Down
2 changes: 0 additions & 2 deletions api/api/settings_quick_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,3 @@
CELERY_EMAIL_MESSAGE_EXTRA_ATTRIBUTES = ["connection"]

LIMIT_USER_DOMAIN_COUNT_DEFAULT = 15

PCH_API = "http://api.invalid"
1 change: 0 additions & 1 deletion api/api/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from django.urls import include, path


#
# On Reversing URLs
# =================
Expand Down
2 changes: 1 addition & 1 deletion api/desecapi/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ class AppConfig(DjangoAppConfig):
name = "desecapi"

def ready(self):
from desecapi import signals # connect signals
from desecapi import signals # noqa: F401 (connect signals)
10 changes: 6 additions & 4 deletions api/desecapi/authentication.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import base64
from datetime import datetime, UTC
from datetime import UTC, datetime
from ipaddress import ip_address

from django.contrib.auth.hashers import PBKDF2PasswordHasher
from django.utils import timezone
from rest_framework import exceptions, HTTP_HEADER_ENCODING
from rest_framework import HTTP_HEADER_ENCODING, exceptions
from rest_framework.authentication import (
BaseAuthentication,
BasicAuthentication,
get_authorization_header,
)
from rest_framework.authentication import (
TokenAuthentication as RestFrameworkTokenAuthentication,
BasicAuthentication,
)

from desecapi.models import Domain, Token
Expand Down Expand Up @@ -184,7 +186,7 @@ def authenticate_credentials(self, context):

# When user.is_active is None, activation is pending. We need to admit them to finish activation, so only
# reject strictly False. There are permissions to make sure that such accounts can't do anything else.
if user.is_active == False:
if user.is_active == False: # noqa: E712
raise exceptions.AuthenticationFailed("User inactive.")
return user, None

Expand Down
4 changes: 2 additions & 2 deletions api/desecapi/crypto.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from base64 import urlsafe_b64encode

from cryptography.fernet import Fernet, InvalidToken
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.kbkdf import CounterLocation, KBKDFHMAC, Mode
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.kbkdf import KBKDFHMAC, CounterLocation, Mode
from django.conf import settings
from django.utils.encoding import force_bytes

Expand Down
2 changes: 1 addition & 1 deletion api/desecapi/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ def get_traceback_data(self):
if self.request is not None:
try:
data["request_meta"]["_body"] = self.request.body
except:
except Exception:
data["request_meta"]["_body"] = None
return data
26 changes: 14 additions & 12 deletions api/desecapi/dns.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import re
import struct

from ipaddress import IPv6Address

import dns
import dns.dnssec
import dns.name
import dns.rdtypes.txtbase, dns.rdtypes.svcbbase
import dns.rdtypes.ANY.CERT, dns.rdtypes.ANY.CNAME, dns.rdtypes.ANY.MX, dns.rdtypes.ANY.NS
import dns.rdtypes.IN.AAAA, dns.rdtypes.IN.SRV
import dns.rdtypes.ANY.CERT
import dns.rdtypes.ANY.CNAME
import dns.rdtypes.ANY.MX
import dns.rdtypes.ANY.NS
import dns.rdtypes.IN.AAAA
import dns.rdtypes.IN.SRV
import dns.rdtypes.svcbbase
import dns.rdtypes.txtbase


def _strip_quotes_decorator(func):
Expand Down Expand Up @@ -45,11 +49,9 @@ def to_text(self, origin=None, relativize=True, **kw):
algorithm = str(
self.algorithm
) # upstream implementation calls dns.dnssec.algorithm_to_text
return "%s %d %s %s" % (
certificate_type,
self.key_tag,
algorithm,
dns.rdata._base64ify(self.certificate, **kw),
return (
f"{certificate_type} {self.key_tag:d} {algorithm} "
f"{dns.rdata._base64ify(self.certificate, **kw)}"
)


Expand Down Expand Up @@ -94,9 +96,9 @@ def from_text(cls, rdclass, rdtype, tok, origin=None, relativize=True):
def _to_wire(self, file, compress=None, origin=None, canonicalize=False):
for long_s in self.strings:
for s in [long_s[i : i + 255] for i in range(0, max(len(long_s), 1), 255)]:
l = len(s)
assert l < 256
file.write(struct.pack("!B", l))
length = len(s)
assert length < 256
file.write(struct.pack("!B", length))
file.write(s)


Expand Down
10 changes: 1 addition & 9 deletions api/desecapi/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class RequestEntityTooLarge(APIException):
default_code = "too_large"


class ExternalAPIException(APIException):
class PDNSException(APIException):
def __init__(self, response=None):
self.response = response
detail = (
Expand All @@ -26,14 +26,6 @@ def __init__(self, response=None):
return super().__init__(detail)


class PDNSException(ExternalAPIException):
pass


class PCHException(ExternalAPIException):
pass


class ConcurrencyException(APIException):
status_code = status.HTTP_429_TOO_MANY_REQUESTS
default_detail = "Too many concurrent requests."
Expand Down
3 changes: 0 additions & 3 deletions api/desecapi/mail_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
from django.core.mail.backends.base import BaseEmailBackend
from djcelery_email.utils import dict_to_email, email_to_dict

from desecapi import metrics


logger = logging.getLogger(__name__)


Expand Down
6 changes: 3 additions & 3 deletions api/desecapi/management/commands/align-catalog-zone.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

from desecapi.exceptions import PDNSException
from desecapi.pdns import (
NSLORD,
NSMASTER,
_pdns_delete,
_pdns_get,
_pdns_post,
NSLORD,
NSMASTER,
pdns_id,
construct_catalog_rrset,
pdns_id,
)


Expand Down
5 changes: 4 additions & 1 deletion api/desecapi/management/commands/check-secondaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
from socket import gethostbyname
from time import sleep

import dns.exception
import dns.message
import dns.query
import dns.rdatatype
from django.conf import settings
from django.core.mail import get_connection, mail_admins
from django.core.management import BaseCommand
from django.utils import timezone
import dns.exception, dns.message, dns.query, dns.rdatatype

from desecapi import pdns
from desecapi.models import Domain
Expand Down
4 changes: 3 additions & 1 deletion api/desecapi/management/commands/chores.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import time
from socket import gethostbyname

import dns.message
import dns.query
import dns.rdatatype
from django.conf import settings
from django.core.mail import get_connection, mail_admins
from django.core.management import BaseCommand
from django.utils import timezone
import dns.message, dns.rdatatype, dns.query

from desecapi import models
from desecapi.pdns_change_tracker import PDNSChangeTracker
Expand Down
2 changes: 1 addition & 1 deletion api/desecapi/management/commands/outreach-email.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def handle(self, *args, **options):
subject = None

base_file = f"emails/{reason}/content.txt"
template_code = '{%% extends "%s" %%}' % base_file
template_code = f'{{% extends "{base_file}" %}}'
if content:
template_code += "{% block content %}" + content + "{% endblock %}"
template = engines["django"].from_string(template_code)
Expand Down
1 change: 0 additions & 1 deletion api/desecapi/management/commands/scavenge-unused.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from desecapi import models, serializers, views
from desecapi.pdns_change_tracker import PDNSChangeTracker


fresh_days = 183
notice_days_notify = 28
notice_days_warn = 7
Expand Down
2 changes: 1 addition & 1 deletion api/desecapi/management/commands/stop-abuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.core.management import BaseCommand
from django.db.models import Q

from desecapi.models import BlockedSubnet, Domain, RR, RRset, User
from desecapi.models import RR, BlockedSubnet, Domain, RRset, User
from desecapi.pdns_change_tracker import PDNSChangeTracker


Expand Down
8 changes: 4 additions & 4 deletions api/desecapi/management/commands/sync-from-pdns.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.db import transaction

from desecapi import pdns
from desecapi.models import Domain, RRset, RR, RR_SET_TYPES_AUTOMATIC
from desecapi.models import RR, RR_SET_TYPES_AUTOMATIC, Domain, RRset


class Command(BaseCommand):
Expand All @@ -24,16 +24,16 @@ def handle(self, *args, **options):

for domain_name in options["domain-name"]:
if domain_name not in domain_names:
raise CommandError("{} is not a known domain".format(domain_name))
raise CommandError(f"{domain_name} is not a known domain")

for domain in domains:
self.stdout.write("%s ..." % domain.name, ending="")
self.stdout.write(f"{domain.name} ...", ending="")
try:
self._sync_domain(domain)
self.stdout.write(" synced")
except Exception as e:
self.stdout.write(" failed")
msg = "Error while processing {}: {}".format(domain.name, e)
msg = f"Error while processing {domain.name}: {e}"
raise CommandError(msg)

@staticmethod
Expand Down
10 changes: 5 additions & 5 deletions api/desecapi/management/commands/sync-to-pdns.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ def handle(self, *args, **options):

for domain_name in options["domain-name"]:
if domain_name not in domain_names:
raise CommandError("{} is not a known domain".format(domain_name))
raise CommandError(f"{domain_name} is not a known domain")

catalog_alignment = False
for domain in domains:
self.stdout.write("%s ..." % domain.name, ending="")
self.stdout.write(f"{domain.name} ...", ending="")
try:
created = self._sync_domain(domain)
if created:
self.stdout.write(f" created (was missing) ...", ending="")
self.stdout.write(" created (was missing) ...", ending="")
catalog_alignment = True
self.stdout.write(" synced")
except Exception as e:
self.stdout.write(" failed")
msg = "Error while processing {}: {}".format(domain.name, e)
msg = f"Error while processing {domain.name}: {e}"
raise CommandError(msg)

if catalog_alignment:
Expand Down Expand Up @@ -76,7 +76,7 @@ def _sync_domain(domain):
domain.name, set(), modifications, deletions
).pdns_do()
pdns._pdns_put(
pdns.NSMASTER, "/zones/{}/axfr-retrieve".format(pdns.pdns_id(domain.name))
pdns.NSMASTER, f"/zones/{pdns.pdns_id(domain.name)}/axfr-retrieve"
)

return created
Loading
Loading