Skip to content

Commit

Permalink
refactor for python 3.12
Browse files Browse the repository at this point in the history
  • Loading branch information
teemukataja committed Jan 5, 2024
1 parent 808f6c8 commit 64b1704
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 95 deletions.
6 changes: 3 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.8-alpine3.13 as BUILD
FROM python:3.12.1-alpine3.19 as BUILD

RUN apk add --update \
&& apk add --no-cache --virtual build-base libressl-dev libffi-dev gcc musl-dev python3-dev \
Expand All @@ -9,11 +9,11 @@ COPY requirements.txt /root/requirements.txt
RUN pip install --upgrade pip && \
pip install -r /root/requirements.txt

FROM python:3.8-alpine3.13
FROM python:3.12.1-alpine3.19

RUN apk add --no-cache --update bash

COPY --from=BUILD /usr/local/lib/python3.8/ /usr/local/lib/python3.8/
COPY --from=BUILD /usr/local/lib/python3.12/ /usr/local/lib/python3.12/

COPY --from=BUILD /usr/local/bin/uvicorn /usr/local/bin/

Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@
Tiny RP is a small OpenID Connect Relying Party client that authenticates the user at the configured OpenID provider and saves the user's `id_token` and `access_token` to cookies.

## Installation
- Developed with `python 3.8.5`
- Developed with `python 3.12.1`
```
pip install --upgrade pip
pip install -r requirements.txt
```
Dependencies are listed in `requirements.dev.txt`, while versions are set for production in `requirements.txt`.

## Configuration
Configuration variables are set in [config.json](config.json), which resides at the root of the directory.
Expand Down
144 changes: 77 additions & 67 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import logging

from urllib.parse import urlencode
from distutils.util import strtobool
from typing import Tuple

import httpx
Expand All @@ -17,12 +16,30 @@
from fastapi.responses import PlainTextResponse, RedirectResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware

# the web app
app = FastAPI()

# distutils.util.strtobool was deprecated in python 3.12 here is the source code for the simple function
# https://github.com/pypa/distutils/blob/94942032878d431cee55adaab12a8bd83549a833/distutils/util.py#L340-L353
def strtobool(val):
"""Convert a string representation of truth to true (1) or false (0).
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
'val' is anything else.
"""
val = val.lower()
if val in ("y", "yes", "t", "true", "on", "1"):
return 1
elif val in ("n", "no", "f", "false", "off", "0"):
return 0
else:
raise ValueError("invalid truth value {!r}".format(val))


# logging
formatting = '[%(asctime)s][%(name)s][%(process)d %(processName)s][%(levelname)-8s] (L:%(lineno)s) %(module)s | %(funcName)s: %(message)s'
logging.basicConfig(level=logging.DEBUG if bool(strtobool(os.environ.get('DEBUG', 'False'))) else logging.INFO, format=formatting)
formatting = "[%(asctime)s][%(name)s][%(process)d %(processName)s][%(levelname)-8s] (L:%(lineno)s) %(module)s | %(funcName)s: %(message)s"
logging.basicConfig(
level=logging.DEBUG if bool(strtobool(os.environ.get("DEBUG", "False"))) else logging.INFO, format=formatting
)
LOG = logging.getLogger("tiny-rp")

# configuration
Expand All @@ -39,22 +56,11 @@
sys.exit(e)


@app.on_event("startup")
async def startup_event():
def get_configs():
"""Request OpenID configuration from OpenID provider."""
# add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=CONFIG["cors_domains"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# get missing OIDC configurations
async with httpx.AsyncClient(verify=False) as client:
# request OpenID provider endpoints from their configuration
with httpx.Client(verify=False) as client:
LOG.debug(f"requesting OpenID configuration from {CONFIG['url_oidc']}")
response = await client.get(CONFIG["url_oidc"])
response = client.get(CONFIG["url_oidc"])
if response.status_code == 200:
# store URLs for later use
LOG.debug("OpenID configuration received")
Expand All @@ -70,6 +76,20 @@ async def startup_event():
sys.exit(f"failed to retrieve OIDC configuration: {response.status_code}")


get_configs()

# the web app
app = FastAPI()
# add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=CONFIG["cors_domains"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


@app.get("/")
async def index_endpoint():
"""Index can be used as a health check endpoint."""
Expand All @@ -91,7 +111,7 @@ async def login_endpoint():
"response_type": "code",
"state": state,
"redirect_uri": CONFIG["url_callback"],
"scope": CONFIG["scope"]
"scope": CONFIG["scope"],
}

# prepare the redirection response
Expand All @@ -100,12 +120,9 @@ async def login_endpoint():
response = RedirectResponse(url)

# store state cookie for callback verification
response.set_cookie(key="oidc_state",
value=state,
max_age=300,
httponly=True,
secure=True,
domain=CONFIG.get("cookie_domain", None))
response.set_cookie(
key="oidc_state", value=state, max_age=300, httponly=True, secure=True, domain=CONFIG.get("cookie_domain", None)
)

# redirect user to sign in at OpenID provider
LOG.debug("redirecting to OpenID provider")
Expand Down Expand Up @@ -158,24 +175,30 @@ async def callback_endpoint(oidc_state: str = Cookie(""), state: str = "", code:
response = RedirectResponse(CONFIG["url_redirect"])

# store tokens to cookies
response.set_cookie(key="id_token",
value=id_token,
max_age=3600,
httponly=True,
secure=True,
domain=CONFIG.get("cookie_domain", None))
response.set_cookie(key="access_token",
value=access_token,
max_age=3600,
httponly=True,
secure=True,
domain=CONFIG.get("cookie_domain", None))
response.set_cookie(key="logged_in",
value="True",
max_age=3600,
httponly=False,
secure=True,
domain=CONFIG.get("cookie_domain", None))
response.set_cookie(
key="id_token",
value=id_token,
max_age=3600,
httponly=True,
secure=True,
domain=CONFIG.get("cookie_domain", None),
)
response.set_cookie(
key="access_token",
value=access_token,
max_age=3600,
httponly=True,
secure=True,
domain=CONFIG.get("cookie_domain", None),
)
response.set_cookie(
key="logged_in",
value="True",
max_age=3600,
httponly=False,
secure=True,
domain=CONFIG.get("cookie_domain", None),
)

# redirect user
LOG.debug(f"redirecting to {CONFIG['url_redirect']}")
Expand All @@ -194,24 +217,15 @@ async def logout_endpoint(id_token: str = Cookie(""), access_token: str = Cookie
response = RedirectResponse(CONFIG["url_redirect"])

# overwrite cookies with instantly expiring ones
response.set_cookie(key="id_token",
value="",
max_age=0,
httponly=True,
secure=True,
domain=CONFIG.get("cookie_domain", None))
response.set_cookie(key="access_token",
value="",
max_age=0,
httponly=True,
secure=True,
domain=CONFIG.get("cookie_domain", None))
response.set_cookie(key="logged_in",
value="",
max_age=0,
httponly=False,
secure=True,
domain=CONFIG.get("cookie_domain", None))
response.set_cookie(
key="id_token", value="", max_age=0, httponly=True, secure=True, domain=CONFIG.get("cookie_domain", None)
)
response.set_cookie(
key="access_token", value="", max_age=0, httponly=True, secure=True, domain=CONFIG.get("cookie_domain", None)
)
response.set_cookie(
key="logged_in", value="", max_age=0, httponly=False, secure=True, domain=CONFIG.get("cookie_domain", None)
)

# redirect user
LOG.debug(f"redirecting to {CONFIG['url_redirect']}")
Expand All @@ -225,11 +239,7 @@ async def request_tokens(code: str) -> Tuple[str, str]:
# set up basic auth and payload
auth = httpx.BasicAuth(username=CONFIG["client_id"], password=CONFIG["client_secret"])
LOG.debug("basic auth is set")
data = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": CONFIG["url_callback"]
}
data = {"grant_type": "authorization_code", "code": code, "redirect_uri": CONFIG["url_callback"]}
LOG.debug(f"post payload: {data}")

async with httpx.AsyncClient(auth=auth, verify=False) as client:
Expand Down
3 changes: 0 additions & 3 deletions requirements.dev.txt

This file was deleted.

19 changes: 3 additions & 16 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
anyio==3.5.0
asgiref==3.5.0
certifi==2021.10.8
charset-normalizer==2.0.12
click==8.1.2
fastapi==0.75.1
h11==0.12.0
httpcore==0.14.7
httpx==0.22.0
idna==3.3
pydantic==1.9.0
rfc3986==1.5.0
sniffio==1.2.0
starlette==0.17.1
typing_extensions==4.1.1
uvicorn==0.17.6
fastapi
httpx
uvicorn
2 changes: 1 addition & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"url_callback": "http://localhost:8080/callback",
"url_redirect": "",
"scope": "openid",
"cookie_domain": ""
"cookie_domain": "",
}


Expand Down
13 changes: 10 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
[tox]
envlist = flake8,mypy,bandit,unit_tests
envlist = black,flake8,mypy,bandit,unit_tests
skipsdist = True

; python code style validation
; format validation
[testenv:black]
skip_install = true
deps =
black
commands = black . -l 120 --check

; style validation
[flake8]
skip_install = true
deps =
Expand Down Expand Up @@ -32,4 +39,4 @@ commands = pytest tests

[gh-actions]
python =
3.8: flake8,mypy,bandit,unit_tests
3.12: black,flake8,mypy,bandit,unit_tests

0 comments on commit 64b1704

Please sign in to comment.