Merge branch 'main' into ui

# Conflicts:
#	.PKGBUILD/PKGBUILD
This commit is contained in:
Oscar Krause 2024-11-21 11:14:32 +01:00
commit f95ba1347f
8 changed files with 85 additions and 46 deletions

View File

@ -1,10 +0,0 @@
# https://packages.ubuntu.com
fastapi==0.91.0
uvicorn[standard]==0.15.0
python-jose[pycryptodome]==3.3.0
pycryptodome==3.11.0
python-dateutil==2.8.2
sqlalchemy==1.4.46
markdown==3.4.3
python-dotenv==0.21.0
jinja2==3.1.2

View File

@ -1,10 +0,0 @@
# https://packages.ubuntu.com
fastapi==0.101.0
uvicorn[standard]==0.23.2
python-jose[pycryptodome]==3.3.0
pycryptodome==3.11.0
python-dateutil==2.8.2
sqlalchemy==1.4.47
markdown==3.4.4
python-dotenv==1.0.0
jinja2==3.1.2

View File

@ -52,6 +52,7 @@ package() {
install -Dm755 "$srcdir/$pkgname/app/main.py" "$pkgdir/opt/$pkgname/main.py" install -Dm755 "$srcdir/$pkgname/app/main.py" "$pkgdir/opt/$pkgname/main.py"
install -Dm755 "$srcdir/$pkgname/app/orm.py" "$pkgdir/opt/$pkgname/orm.py" install -Dm755 "$srcdir/$pkgname/app/orm.py" "$pkgdir/opt/$pkgname/orm.py"
install -Dm755 "$srcdir/$pkgname/app/util.py" "$pkgdir/opt/$pkgname/util.py" install -Dm755 "$srcdir/$pkgname/app/util.py" "$pkgdir/opt/$pkgname/util.py"
install -Dm755 "$srcdir/$pkgname/app/middleware.py" "$pkgdir/opt/$pkgname/middleware.py"
# copy static asset files # copy static asset files
install -Dm755 "$srcdir/$pkgname/app/static/assets/css/bootstrap.min.css" "$pkgdir/opt/$pkgname/static/assets/css/bootstrap.min.css" install -Dm755 "$srcdir/$pkgname/app/static/assets/css/bootstrap.min.css" "$pkgdir/opt/$pkgname/static/assets/css/bootstrap.min.css"

View File

@ -144,11 +144,9 @@ test:
matrix: matrix:
- IMAGE: [ 'python:3.12-slim-bookworm' ] - IMAGE: [ 'python:3.12-slim-bookworm' ]
REQUIREMENTS: [ 'requirements.txt' ] REQUIREMENTS: [ 'requirements.txt' ]
- IMAGE: [ 'debian:bookworm' ] - IMAGE: [ 'debian:bookworm' ] # EOL: June 06, 2026
REQUIREMENTS: [ '.DEBIAN/requirements-bookworm-12.txt' ] REQUIREMENTS: [ '.DEBIAN/requirements-bookworm-12.txt' ]
- IMAGE: [ 'ubuntu:23.10' ] - IMAGE: [ 'ubuntu:24.04' ] # EOL: April 2036
REQUIREMENTS: [ '.DEBIAN/requirements-ubuntu-23.10.txt' ]
- IMAGE: [ 'ubuntu:24.04' ]
REQUIREMENTS: [ '.DEBIAN/requirements-ubuntu-24.04.txt' ] REQUIREMENTS: [ '.DEBIAN/requirements-ubuntu-24.04.txt' ]
- IMAGE: [ 'ubuntu:24.10' ] - IMAGE: [ 'ubuntu:24.10' ]
REQUIREMENTS: [ '.DEBIAN/requirements-ubuntu-24.10.txt' ] REQUIREMENTS: [ '.DEBIAN/requirements-ubuntu-24.10.txt' ]

View File

@ -330,11 +330,12 @@ Packages are available here:
Successful tested with: Successful tested with:
- Debian 12 (Bookworm) (EOL: tba.) - **Debian 12 (Bookworm)** (EOL: June 06, 2026)
- Ubuntu 22.10 (Kinetic Kudu) (EOL: July 20, 2023) - *Ubuntu 22.10 (Kinetic Kudu)* (EOL: July 20, 2023)
- Ubuntu 23.04 (Lunar Lobster) (EOL: January 2024) - *Ubuntu 23.04 (Lunar Lobster)* (EOL: January 2024)
- Ubuntu 23.10 (Mantic Minotaur) (EOL: July 2024) - *Ubuntu 23.10 (Mantic Minotaur)* (EOL: July 2024)
- Ubuntu 24.04 (Noble Numbat) (EOL: April 2036) - **Ubuntu 24.04 (Noble Numbat)** (EOL: April 2036)
- *Ubuntu 24.10 (Oracular Oriole)* (EOL: tba.)
Not working with: Not working with:
@ -410,21 +411,22 @@ After first success you have to replace `--issue` with `--renew`.
# Configuration # Configuration
| Variable | Default | Usage | | Variable | Default | Usage |
|------------------------|----------------------------------------|------------------------------------------------------------------------------------------------------| |--------------------------|----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| `DEBUG` | `false` | Toggles `fastapi` debug mode | | `DEBUG` | `false` | Toggles `fastapi` debug mode |
| `DLS_URL` | `localhost` | Used in client-token to tell guest driver where dls instance is reachable | | `DLS_URL` | `localhost` | Used in client-token to tell guest driver where dls instance is reachable |
| `DLS_PORT` | `443` | Used in client-token to tell guest driver where dls instance is reachable | | `DLS_PORT` | `443` | Used in client-token to tell guest driver where dls instance is reachable |
| `TOKEN_EXPIRE_DAYS` | `1` | Client auth-token validity (used for authenticate client against api, **not `.tok` file!**) | | `TOKEN_EXPIRE_DAYS` | `1` | Client auth-token validity (used for authenticate client against api, **not `.tok` file!**) |
| `LEASE_EXPIRE_DAYS` | `90` | Lease time in days | | `LEASE_EXPIRE_DAYS` | `90` | Lease time in days |
| `LEASE_RENEWAL_PERIOD` | `0.15` | The percentage of the lease period that must elapse before a licensed client can renew a license \*1 | | `LEASE_RENEWAL_PERIOD` | `0.15` | The percentage of the lease period that must elapse before a licensed client can renew a license \*1 |
| `DATABASE` | `sqlite:///db.sqlite` | See [official SQLAlchemy docs](https://docs.sqlalchemy.org/en/14/core/engines.html) | | `DATABASE` | `sqlite:///db.sqlite` | See [official SQLAlchemy docs](https://docs.sqlalchemy.org/en/14/core/engines.html) |
| `CORS_ORIGINS` | `https://{DLS_URL}` | Sets `Access-Control-Allow-Origin` header (comma separated string) \*2 | | `CORS_ORIGINS` | `https://{DLS_URL}` | Sets `Access-Control-Allow-Origin` header (comma separated string) \*2 |
| `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid | | `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid |
| `INSTANCE_REF` | `10000000-0000-0000-0000-000000000001` | Instance identification uuid | | `INSTANCE_REF` | `10000000-0000-0000-0000-000000000001` | Instance identification uuid |
| `ALLOTMENT_REF` | `20000000-0000-0000-0000-000000000001` | Allotment identification uuid | | `ALLOTMENT_REF` | `20000000-0000-0000-0000-000000000001` | Allotment identification uuid |
| `INSTANCE_KEY_RSA` | `<app-dir>/cert/instance.private.pem` | Site-wide private RSA key for singing JWTs \*3 | | `INSTANCE_KEY_RSA` | `<app-dir>/cert/instance.private.pem` | Site-wide private RSA key for singing JWTs \*3 |
| `INSTANCE_KEY_PUB` | `<app-dir>/cert/instance.public.pem` | Site-wide public key \*3 | | `INSTANCE_KEY_PUB` | `<app-dir>/cert/instance.public.pem` | Site-wide public key \*3 |
| `SUPPORT_MALFORMED_JSON` | `false` | Support parsing for mal formatted "mac_address_list" ([Issue](https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/issues/1)) |
\*1 For example, if the lease period is one day and the renewal period is 20%, the client attempts to renew its license \*1 For example, if the lease period is one day and the renewal period is 20%, the client attempts to renew its license
every 4.8 hours. If network connectivity is lost, the loss of connectivity is detected during license renewal and the every 4.8 hours. If network connectivity is lost, the loss of connectivity is detected during license renewal and the

View File

@ -100,6 +100,11 @@ app.add_middleware(
allow_methods=['*'], allow_methods=['*'],
allow_headers=['*'], allow_headers=['*'],
) )
if bool(env('SUPPORT_MALFORMED_JSON', False)):
from middleware import PatchMalformedJsonMiddleware
logger.info(f'Enabled "PatchMalformedJsonMiddleware"!')
app.add_middleware(PatchMalformedJsonMiddleware, enabled=True)
# Helper # Helper

43
app/middleware.py Normal file
View File

@ -0,0 +1,43 @@
import json
import logging
import re
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
logger = logging.getLogger(__name__)
class PatchMalformedJsonMiddleware(BaseHTTPMiddleware):
# see oscar.krause/fastapi-dls#1
REGEX = '(\"mac_address_list\"\:\s?\[)([\w\d])'
def __init__(self, app, enabled: bool):
super().__init__(app)
self.enabled = enabled
async def dispatch(self, request: Request, call_next):
body = await request.body()
content_type = request.headers.get('Content-Type')
if self.enabled and content_type == 'application/json':
body = body.decode()
try:
json.loads(body)
except json.decoder.JSONDecodeError:
logger.warning(f'Malformed json received! Try to fix it, "PatchMalformedJsonMiddleware" is enabled.')
s = PatchMalformedJsonMiddleware.fix_json(body)
logger.debug(f'Fixed JSON: "{s}"')
s = json.loads(s) # ensure json is now valid
# set new body
request._body = json.dumps(s).encode('utf-8')
response = await call_next(request)
return response
@staticmethod
def fix_json(s: str) -> str:
s = s.replace('\t', '')
s = s.replace('\n', '')
return re.sub(PatchMalformedJsonMiddleware.REGEX, r'\1"\2', s)

View File

@ -1,7 +1,8 @@
import sys
from base64 import b64encode as b64enc from base64 import b64encode as b64enc
from hashlib import sha256
from calendar import timegm from calendar import timegm
from datetime import datetime from datetime import datetime
from hashlib import sha256
from os.path import dirname, join from os.path import dirname, join
from uuid import uuid4, UUID from uuid import uuid4, UUID
@ -9,7 +10,6 @@ from dateutil.relativedelta import relativedelta
from jose import jwt, jwk from jose import jwt, jwk
from jose.constants import ALGORITHMS from jose.constants import ALGORITHMS
from starlette.testclient import TestClient from starlette.testclient import TestClient
import sys
# add relative path to use packages as they were in the app/ dir # add relative path to use packages as they were in the app/ dir
sys.path.append('../') sys.path.append('../')
@ -18,6 +18,7 @@ sys.path.append('../app')
from app import main from app import main
from app.util import load_key from app.util import load_key
# main.app.add_middleware(PatchMalformedJsonMiddleware, enabled=True)
client = TestClient(main.app) client = TestClient(main.app)
ORIGIN_REF, ALLOTMENT_REF, SECRET = str(uuid4()), '20000000-0000-0000-0000-000000000001', 'HelloWorld' ORIGIN_REF, ALLOTMENT_REF, SECRET = str(uuid4()), '20000000-0000-0000-0000-000000000001', 'HelloWorld'
@ -106,6 +107,15 @@ def test_auth_v1_origin():
assert response.json().get('origin_ref') == ORIGIN_REF assert response.json().get('origin_ref') == ORIGIN_REF
def test_auth_v1_origin_malformed_json(): # see oscar.krause/fastapi-dls#1
from middleware import PatchMalformedJsonMiddleware
# test regex (temporary, until this section is merged into main.py
s = '{"environment": {"fingerprint": {"mac_address_list": [ff:ff:ff:ff:ff:ff"]}}'
replaced = PatchMalformedJsonMiddleware.fix_json(s)
assert replaced == '{"environment": {"fingerprint": {"mac_address_list": ["ff:ff:ff:ff:ff:ff"]}}'
def auth_v1_origin_update(): def auth_v1_origin_update():
payload = { payload = {
"registration_pending": False, "registration_pending": False,