diff --git a/.github/workflows/build/action.yml b/.github/workflows/build/action.yml index b4042ec45..b7df8d581 100644 --- a/.github/workflows/build/action.yml +++ b/.github/workflows/build/action.yml @@ -15,4 +15,4 @@ runs: run: docker-compose build ${{ inputs.images }} - name: Build e2e2 Image shell: bash - run: docker-compose -f docker-compose.yml -f docker-compose.test-e2e2.yml build test-e2e2 + run: docker-compose -f docker-compose.yml -f docker-compose.test-e2e2.yml build libfaketime && docker-compose -f docker-compose.yml -f docker-compose.test-e2e2.yml build test-e2e2 diff --git a/README.md b/README.md index fd9d3baad..81118f496 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ While there are certainly many ways to get started hacking desec-stack, here is For desec-stack, [docker](https://docs.docker.com/install/linux/docker-ce/ubuntu/) and [docker-compose](https://docs.docker.com/compose/install/) are required. Further tools that are required to start hacking are git and curl. Recommended, but not strictly required for desec-stack development is to use certbot along with Let's Encrypt and PyCharm. - jq, httpie, libmariadbclient-dev, libpq-dev, python3-dev (>= 3.8) and python3-venv (>= 3.8) are useful if you want to follow this guide. + jq, httpie, libmariadbclient-dev, libpq-dev, python3-dev (>= 3.11) and python3-venv (>= 3.11) are useful if you want to follow this guide. The webapp requires nodejs. To install everything you need for this guide except docker and docker-compose, use sudo apt install certbot curl git httpie jq libmariadbclient-dev libpq-dev nodejs npm python3-dev python3-venv libmemcached-dev @@ -324,7 +324,7 @@ While there are certainly many ways to get started hacking desec-stack, here is In the project root, cd api - python3 -m venv venv + python3 -m venv venv # Python >= 3.11 source venv/bin/activate pip install wheel pip install -r requirements.txt diff --git a/api/Dockerfile b/api/Dockerfile index 8419a18c9..fcf54a5e4 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10-alpine +FROM python:3.11-alpine COPY --from=trajano/alpine-libfaketime /faketime.so /lib/libfaketime.so RUN mkdir -p /etc/faketime diff --git a/api/desecapi/authentication.py b/api/desecapi/authentication.py index 4391fdee5..fef5050b5 100644 --- a/api/desecapi/authentication.py +++ b/api/desecapi/authentication.py @@ -1,5 +1,5 @@ import base64 -import datetime +from datetime import datetime, UTC from ipaddress import ip_address from django.contrib.auth.hashers import PBKDF2PasswordHasher @@ -178,9 +178,7 @@ def authenticate_credentials(self, context): serializer.is_valid(raise_exception=True) user = serializer.validated_data["user"] - email_verified = datetime.datetime.fromtimestamp( - serializer.timestamp, datetime.timezone.utc - ) + email_verified = datetime.fromtimestamp(serializer.timestamp, UTC) user.email_verified = max(user.email_verified or email_verified, email_verified) user.save() diff --git a/api/desecapi/tests/base.py b/api/desecapi/tests/base.py index b45b8eeb4..b2fd91932 100644 --- a/api/desecapi/tests/base.py +++ b/api/desecapi/tests/base.py @@ -979,7 +979,7 @@ def random_domain_name(cls, suffix=None): if not suffix: suffix = cls.PUBLIC_SUFFIXES if isinstance(suffix, set): - suffix = random.sample(suffix, 1)[0] + suffix = random.sample(list(suffix), 1)[0] return ( random.choice(string.ascii_letters) + cls.random_string() diff --git a/api/desecapi/tests/test_rrsets.py b/api/desecapi/tests/test_rrsets.py index 0fbe659ca..7acdc0bb3 100644 --- a/api/desecapi/tests/test_rrsets.py +++ b/api/desecapi/tests/test_rrsets.py @@ -1242,7 +1242,7 @@ def test_update_my_rr_set_with_invalid_payload_type(self): self.my_rr_set_domain.name, subname, "A", data ) self.assertStatus(response, status.HTTP_400_BAD_REQUEST) - self.assertEquals( + self.assertEqual( response.data["non_field_errors"][0], "Invalid data. Expected a dictionary, but got list.", ) @@ -1252,7 +1252,7 @@ def test_update_my_rr_set_with_invalid_payload_type(self): self.my_rr_set_domain.name, subname, "A", data ) self.assertStatus(response, status.HTTP_400_BAD_REQUEST) - self.assertEquals( + self.assertEqual( response.data["non_field_errors"][0], "Invalid data. Expected a dictionary, but got str.", ) @@ -1333,19 +1333,19 @@ def test_update_essential_properties(self): data = {"records": ["3.2.3.4"], "ttl": 3620, "subname": "test2", "type": "A"} response = self.client.patch(url, data) self.assertStatus(response, status.HTTP_400_BAD_REQUEST) - self.assertEquals(response.data["subname"][0].code, "read-only-on-update") + self.assertEqual(response.data["subname"][0].code, "read-only-on-update") response = self.client.put(url, data) self.assertStatus(response, status.HTTP_400_BAD_REQUEST) - self.assertEquals(response.data["subname"][0].code, "read-only-on-update") + self.assertEqual(response.data["subname"][0].code, "read-only-on-update") # Changing the type is expected to cause an error data = {"records": ["3.2.3.4"], "ttl": 3620, "subname": "test", "type": "TXT"} response = self.client.patch(url, data) self.assertStatus(response, status.HTTP_400_BAD_REQUEST) - self.assertEquals(response.data["type"][0].code, "read-only-on-update") + self.assertEqual(response.data["type"][0].code, "read-only-on-update") response = self.client.put(url, data) self.assertStatus(response, status.HTTP_400_BAD_REQUEST) - self.assertEquals(response.data["type"][0].code, "read-only-on-update") + self.assertEqual(response.data["type"][0].code, "read-only-on-update") # Changing "created" is no-op response = self.client.get(url) diff --git a/api/desecapi/tests/test_user_management.py b/api/desecapi/tests/test_user_management.py index 78bf9f27b..c674503e6 100644 --- a/api/desecapi/tests/test_user_management.py +++ b/api/desecapi/tests/test_user_management.py @@ -603,7 +603,7 @@ def test_registration_with_domain(self): domain="co.uk", expect_failure_response=self.assertRegistrationFailureDomainUnavailableResponse, ) - local_public_suffix = random.sample(self.AUTO_DELEGATION_DOMAINS, 1)[0] + local_public_suffix = random.sample(list(self.AUTO_DELEGATION_DOMAINS), 1)[0] with self.get_psl_context_manager(local_public_suffix): self._test_registration_with_domain( domain=self.random_domain_name(suffix=local_public_suffix) diff --git a/api/requirements.txt b/api/requirements.txt index 80c5e47ec..923fa5626 100644 --- a/api/requirements.txt +++ b/api/requirements.txt @@ -1,18 +1,18 @@ captcha~=0.4.0 -celery~=5.2.7 +celery~=5.3.1 coverage~=7.2.7 cryptography~=41.0.1 -Django~=4.1.0 # upgrade once django-pgtrigger is compatible -django-cors-headers~=4.0.0 +Django~=4.2.2 +django-cors-headers~=4.1.0 djangorestframework~=3.14.0 django-celery-email~=3.0.0 django-netfields~=1.3.0 -django-pgtrigger~=4.6.0 +django-pgtrigger~=4.7.0 django-prometheus~=2.3.1 dnspython~=2.3.0 httpretty~=1.0.5 # 1.1 breaks tests. Does not run in production, so stick to it. pyotp~=2.8.0 -psycopg2~=2.9.6 # replace with psycopg (3) once django-pgtrigger is compatible +psycopg~=3.1.9 prometheus-client~=0.17.0 # added to control django-prometheus' dependency version psl-dns~=1.1.0 pylibmc~=1.6.3 diff --git a/docker-compose.test-e2e2.yml b/docker-compose.test-e2e2.yml index f019f292c..c346bd548 100644 --- a/docker-compose.test-e2e2.yml +++ b/docker-compose.test-e2e2.yml @@ -89,6 +89,11 @@ services: - "www.desec.${DESECSTACK_DOMAIN}:${DESECSTACK_IPV4_REAR_PREFIX16}.0.128" - "get.desec.${DESECSTACK_DOMAIN}:${DESECSTACK_IPV4_REAR_PREFIX16}.0.128" + libfaketime: + # trajano/alpine-libfaketime is incompatible is stale; its libfaketime + # version is incompatible with Python 3.11's time.sleep(). Rebuilding helps. + build: https://github.com/trajano/alpine-libfaketime.git + volumes: autocert: faketime: diff --git a/test/e2e2/Dockerfile b/test/e2e2/Dockerfile index 88e15cee8..9e0d7f924 100644 --- a/test/e2e2/Dockerfile +++ b/test/e2e2/Dockerfile @@ -1,8 +1,8 @@ -FROM python:3.9-alpine +FROM python:3.11-alpine RUN apk add --no-cache bash curl -COPY --from=trajano/alpine-libfaketime /faketime.so /lib/libfaketime.so +COPY --from=desec-stack_libfaketime /faketime.so /lib/libfaketime.so RUN mkdir -p /etc/faketime RUN mkdir /e2e diff --git a/test/e2e2/README.md b/test/e2e2/README.md index 58ea17ff6..575ac9f75 100644 --- a/test/e2e2/README.md +++ b/test/e2e2/README.md @@ -6,6 +6,7 @@ A collection of tests against the stack written in python and pytest. The tests can be run from the **CLI** using + docker-compose -f docker-compose.yml -f docker-compose.test-e2e2.yml build libfaketime docker-compose -f docker-compose.yml -f docker-compose.test-e2e2.yml build test-e2e2 docker-compose -f docker-compose.yml -f docker-compose.test-e2e2.yml up test-e2e2 diff --git a/test/e2e2/conftest.py b/test/e2e2/conftest.py index e8c6d517b..0ab35cb7a 100644 --- a/test/e2e2/conftest.py +++ b/test/e2e2/conftest.py @@ -495,9 +495,10 @@ def assert_all_ns(assertion: callable, retry_on=(AssertionError,)): def faketime(t: str): print('FAKETIME', t) - with open('/etc/faketime/faketime.rc', 'w') as f: + with open(os.environ['FAKETIME_TIMESTAMP_FILE'] + '.tmp', 'w') as f: f.write(t + '\n') - + # https://github.com/wolfcw/libfaketime/issues/392#issuecomment-1122344129 + os.rename(os.environ['FAKETIME_TIMESTAMP_FILE'] + '.tmp', os.environ['FAKETIME_TIMESTAMP_FILE']) def faketime_get(): try: diff --git a/www/webapp/package.json b/www/webapp/package.json index ecb81385a..1903a4433 100644 --- a/www/webapp/package.json +++ b/www/webapp/package.json @@ -9,10 +9,10 @@ "lint:fix": "vue-cli-service lint --fix" }, "dependencies": { - "@fontsource/roboto": "^4.5.8", - "@mdi/font": "^6.9.96", + "@fontsource/roboto": "^5.0.3", + "@mdi/font": "^7.2.96", "@mdi/js": "~7.2.96", - "axios": "^0.27.2", + "axios": "^1.4.0", "core-js": "^3.27.1", "javascript-time-ago": "^2.5.9", "pinia": "^2.0.30", diff --git a/www/webapp/src/router/index.js b/www/webapp/src/router/index.js index 18ccf615c..cc9a43ff6 100644 --- a/www/webapp/src/router/index.js +++ b/www/webapp/src/router/index.js @@ -158,7 +158,7 @@ router.beforeEach((to, from, next) => { const user = useUserStore(); if (sessionStorage.getItem('token') && !user.authenticated) { const token = JSON.parse(sessionStorage.getItem('token')); - HTTP.defaults.headers.common['Authorization'] = 'Token ' + token.token; + HTTP.defaults.headers.Authorization = 'Token ' + token.token; user.login(token); recovered = true } diff --git a/www/webapp/src/utils.js b/www/webapp/src/utils.js index e18b58068..bbfc893bd 100644 --- a/www/webapp/src/utils.js +++ b/www/webapp/src/utils.js @@ -9,7 +9,7 @@ export const HTTP = axios.create({ function clearToken() { useUserStore().logout(); - HTTP.defaults.headers.common.Authorization = ''; + HTTP.defaults.headers.Authorization = ''; sessionStorage.removeItem('token'); } diff --git a/www/webapp/src/views/CrudListToken.vue b/www/webapp/src/views/CrudListToken.vue index a4d66813d..94038fe43 100644 --- a/www/webapp/src/views/CrudListToken.vue +++ b/www/webapp/src/views/CrudListToken.vue @@ -46,7 +46,7 @@ export default { value: 'perm_manage_tokens', readonly: false, writeOnCreate: true, - datatype: 'Switchbox', + datatype: 'GenericSwitchbox', searchable: false, advanced: true, }, @@ -98,7 +98,7 @@ export default { sortable: true, value: 'is_valid', readonly: true, - datatype: 'Checkbox', + datatype: 'GenericCheckbox', searchable: false, }, value: { diff --git a/www/webapp/src/views/LoginPage.vue b/www/webapp/src/views/LoginPage.vue index ab76aa31a..58646ed39 100644 --- a/www/webapp/src/views/LoginPage.vue +++ b/www/webapp/src/views/LoginPage.vue @@ -121,7 +121,7 @@ export default { email: this.email, password: this.password, }); - HTTP.defaults.headers.common.Authorization = `Token ${response.data.token}`; + HTTP.defaults.headers.Authorization = `Token ${response.data.token}`; this.user.login(response.data); if (this.useSessionStorage) { sessionStorage.setItem('token', JSON.stringify(response.data));