Compare commits

..

No commits in common. "main" and "0.1.64" have entirely different histories.
main ... 0.1.64

15 changed files with 155 additions and 198 deletions

View file

@ -0,0 +1,93 @@
on:
push:
workflow_dispatch:
inputs:
verbose:
description: "Verbose"
required: false
default: false
type: boolean
jobs:
build:
runs-on: container
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v4
with:
version: "0.5.16"
- name: Install
run: uv sync
- name: Run tests (API call)
run: .venv/bin/pytest -s tests/basic.py
- name: Get version with git describe
id: version
run: |
echo "version=$(git describe)" >> $GITHUB_OUTPUT
echo "$VERSION"
- name: Check if the container should be built
id: builder
env:
RUN: ${{ toJSON(inputs.build || !contains(steps.version.outputs.version, '-')) }}
run: |
echo "run=$RUN" >> $GITHUB_OUTPUT
echo "Run build: $RUN"
- name: Set the version in pyproject.toml (workaround for uv not supporting dynamic version)
if: fromJSON(steps.builder.outputs.run)
env:
VERSION: ${{ steps.version.outputs.version }}
run: sed "s/0.0.0/$VERSION/" -i pyproject.toml
- name: Workaround for bug of podman-login
if: fromJSON(steps.builder.outputs.run)
run: |
mkdir -p $HOME/.docker
echo "{ \"auths\": {} }" > $HOME/.docker/config.json
- name: Log in to the container registry (with another workaround)
if: fromJSON(steps.builder.outputs.run)
uses: actions/podman-login@v1
with:
registry: ${{ vars.REGISTRY }}
username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_PASSWORD }}
auth_file_path: /tmp/auth.json
- name: Build the container image
if: fromJSON(steps.builder.outputs.run)
uses: actions/buildah-build@v1
with:
image: oidc-fastapi-test
oci: true
labels: oidc-fastapi-test
tags: latest ${{ steps.version.outputs.version }}
containerfiles: |
./Containerfile
- name: Push the image to the registry
if: fromJSON(steps.builder.outputs.run)
uses: actions/push-to-registry@v2
with:
registry: "docker://${{ vars.REGISTRY }}/${{ vars.ORGANISATION }}"
image: oidc-fastapi-test
tags: latest ${{ steps.version.outputs.version }}
- name: Build wheel
if: fromJSON(steps.builder.outputs.run)
run: uv build --wheel
- name: Publish Python package (home)
if: fromJSON(steps.builder.outputs.run)
env:
LOCAL_PYPI_TOKEN: ${{ secrets.LOCAL_PYPI_TOKEN }}
run: uv publish --publish-url https://code.philo.ydns.eu/api/packages/philorg/pypi --token $LOCAL_PYPI_TOKEN
continue-on-error: true

View file

@ -0,0 +1,28 @@
on:
push:
workflow_dispatch:
inputs:
verbose:
description: "Verbose"
required: false
default: false
type: boolean
jobs:
test:
runs-on: container
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v4
with:
version: "0.5.16"
- name: Install
run: uv sync
- name: Run tests (API call)
run: .venv/bin/pytest -s tests/basic.py

View file

@ -1,63 +0,0 @@
when:
- event: manual
- event: tag
depends_on:
- test
steps:
python_sync:
image: code.philo.ydns.eu/philorg/uv
volumes:
- uv-cache:/uv-cache
environment:
UV_CACHE_DIR: /uv-cache
UV_LINK_MODE: copy
commands:
- uv sync
python_build:
image: code.philo.ydns.eu/philorg/uv
volumes:
- uv-cache:/uv-cache
environment:
UV_CACHE_DIR: /uv-cache
UV_LINK_MODE: copy
commands:
- uv build --wheel
- uv cache prune --ci
python_publish:
image: code.philo.ydns.eu/philorg/uv
environment:
OWNER: philorg
REGISTRY_URL: https://code.philo.ydns.eu
REGISTRY_TOKEN:
from_secret: registry_token
commands:
- uv publish --publish-url $REGISTRY_URL/api/packages/$OWNER/pypi --token $REGISTRY_TOKEN dist/*.whl
failure: ignore
container_build_publish:
image: quay.io/podman/stable:latest
# Caution: This image is built daily. It might fill up your image store quickly.
#pull: true
volumes:
- containers:/var/lib/containers
- uv:/root/.cache/uv
# Fill in the trusted checkbox in Woodpecker's settings as well
privileged: true
environment:
registry: code.philo.ydns.eu
org: philorg
container_name: oidc-fastapi-test
registry_token:
from_secret: registry_token
commands:
# Login at the registry
- podman login -u __token__ --password $registry_token $registry
# Build the container image
- podman build --volume=/var/lib/containers:/var/lib/containers --tag $registry/$org/$container_name:latest --tag $registry/$org/$container_name:$CI_COMMIT_TAG .
# Push the image
- podman push $registry/$org/$container_name:latest
- podman push $registry/$org/$container_name:$CI_COMMIT_TAG

View file

@ -1,21 +0,0 @@
when:
- event: push
branch: main
- event: manual
- event: tag
steps:
sync:
image: code.philo.ydns.eu/philorg/uv
volumes:
- uv-cache:/uv-cache
environment:
UV_CACHE_DIR: /uv-cache
UV_LINK_MODE: copy
commands:
- uv sync
test:
image: code.philo.ydns.eu/philorg/uv
commands:
- .venv/bin/pytest -s tests/basic.py

View file

@ -1,7 +1,6 @@
FROM docker.io/library/python:3.13-slim
FROM docker.io/library/python:alpine
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
COPY --from=docker.io/python:3.13 /usr/bin/git /usr/local/bin/git
COPY . /app

View file

@ -52,59 +52,31 @@ given by the OIDC providers.
For example:
```yaml
secret_key: AVeryWellKeptSecret
debug_token: no
show_token: yes
log: yes
auth:
oidc:
secret_key: "ASecretNoOneKnows"
show_session_details: yes
providers:
- id: auth0
name: Okta / Auth0
url: https://<your_auth0_app_URL>
public_key_url: https://<your_auth0_app_URL>/pem
client_id: <your_auth0_client_id>
client_secret: client_secret_generated_by_auth0
hint: A hint for test credentials
url: "https://<your_auth0_app_URL>"
client_id: "<your_auth0_client_id>"
client_secret: "client_secret_generated_by_auth0"
hint: "A hint for test credentials"
- id: keycloak
name: Keycloak at somewhere
url: https://<the_keycloak_realm_url>
info_url: https://philo.ydns.eu/auth/realms/test
account_url_template: /account
client_id: <your_keycloak_client_id>
client_secret: <client_secret_generated_by_keycloak>
hint: A hint for test credentials
code_challenge_method: S256
resource_provider_scopes:
- get:time
- get:bs
resource_providers:
- id: <third_party_resource_provider_id>
name: A third party resource provider
base_url: https://some.example.com/
verify_ssl: yes
resources:
- name: Public RS2
resource_name: public
url: resource/public
- name: BS RS2
resource_name: bs
url: resource/bs
- name: Time RS2
resource_name: time
url: resource/time
url: "https://<the_keycloak_realm_url>"
account_url_template: "/account"
client_id: "<your_keycloak_client_id>"
client_secret: "client_secret_generated_by_keycloak"
hint: "User: foo, password: foofoo"
- id: codeberg
disabled: no
name: Codeberg
url: https://codeberg.org
account_url_template: /user/settings
client_id: <your_codeberg_client_id>
client_secret: client_secret_generated_by_codeberg
info_url: https://codeberg.org/login/oauth/keys
session_key: sub
skip_verify_signature: no
url: "https://codeberg.org"
account_url_template: "/user/settings"
client_id: "<your_codeberg_client_id>"
client_secret: "client_secret_generated_by_codeberg"
resources:
- name: List of repos
id: repos
@ -127,5 +99,3 @@ with the setting file in the current working directory:
```sh
podman run -p 8000:80 --env OIDC_TEST_CONFIG_FILE=/app/settings.yaml --mount type=bind,source=settings.yaml,destination=/app/settings.yaml code.philo.ydns.eu/philorg/oidc-fastapi-test:latest
```
[![status-badge](https://code.philo.ydns.eu/woodpecker/api/badges/22/status.svg)](https://code.philo.ydns.eu/woodpecker/repos/22)

View file

@ -1,7 +1,7 @@
[project]
name = "oidc-fastapi-test"
#version = "0.0.0"
dynamic = ["version"]
version = "0.0.0"
# dynamic = ["version"]
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
@ -24,21 +24,14 @@ dependencies = [
oidc-test = "oidc_test.main:main"
[dependency-groups]
dev = ["dunamai>=1.23.0", "ipdb>=0.13.13", "pytest>=8.3.4"]
dev = ["ipdb>=0.13.13", "pytest>=8.3.4"]
[build-system]
requires = ["hatchling", "uv-dynamic-versioning"]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.version]
source = "uv-dynamic-versioning"
[tool.hatch.build.targets.wheel]
packages = ["src/oidc_test"]
package = true
[tool.uv-dynamic-versioning]
style = "semver"
[tool.uv]
package = true

View file

@ -1,11 +0,0 @@
import importlib.metadata
try:
from dunamai import Version, Style
__version__ = Version.from_git().serialize(style=Style.SemVer, dirty=True)
except (ImportError, RuntimeError):
# __name__ could be used if the package name is the same
# as the directory - not the case here
# __version__ = importlib.metadata.version(__name__)
__version__ = importlib.metadata.version("oidc-fastapi-test")

View file

@ -61,34 +61,28 @@ class Provider(AuthProviderSettings):
if self.info_url is not None:
try:
provider_info = await client.get(self.info_url)
except Exception as err:
logger.debug("Provider_info: cannot connect")
logger.exception(err)
except Exception:
raise NoPublicKey
try:
self.info = provider_info.json()
except JSONDecodeError:
logger.debug("Provider_info: cannot decode json response")
raise NoPublicKey
if "public_key" in self.info:
# For Keycloak
try:
public_key = str(self.info["public_key"])
except KeyError:
logger.debug("Provider_info: cannot get public_key")
raise NoPublicKey
elif "keys" in self.info:
# For Forgejo/Gitea
try:
public_key = str(self.info["keys"][0]["n"])
except KeyError:
logger.debug("Provider_info: cannot get key 0.n")
raise NoPublicKey
if self.public_key_url is not None:
resp = await client.get(self.public_key_url)
public_key = resp.text
if public_key is None:
logger.debug("Provider_info: cannot determine public key")
raise NoPublicKey
self.public_key = "\n".join(
["-----BEGIN PUBLIC KEY-----", public_key, "-----END PUBLIC KEY-----"]

View file

@ -5,9 +5,11 @@ import logging
from fastapi import HTTPException, Request, Depends, status
from fastapi.security import OAuth2PasswordBearer
from authlib.integrations.starlette_client import OAuth, OAuthError, StarletteOAuth2App
from authlib.oauth2.rfc6749 import OAuth2Token
from jwt import ExpiredSignatureError, InvalidKeyError, DecodeError, PyJWTError
# from authlib.oauth1.auth import OAuthToken
from authlib.oauth2.rfc6749 import OAuth2Token
from oidc_test.auth.provider import Provider
from oidc_test.models import User
from oidc_test.database import db, TokenNotInDb, UserNotInDB
@ -20,7 +22,7 @@ logger = logging.getLogger("oidc-test")
async def fetch_token(name, request):
assert name is not None
assert request is not None
logger.warning("TODO: fetch_token")
logger.warn("TODO: fetch_token")
...
# if name in oidc_providers:
# model = OAuth2Token
@ -32,10 +34,7 @@ async def fetch_token(name, request):
async def update_token(
provider_id,
token,
refresh_token: str | None = None,
access_token: str | None = None,
provider_id, token, refresh_token: str | None = None, access_token: str | None = None
):
"""Update the token in the database"""
provider = providers[provider_id]

View file

@ -29,7 +29,6 @@ from authlib.oauth2.rfc6749 import OAuth2Token
# from fastapi.security import OpenIdConnect
# from pkce import generate_code_verifier, generate_pkce_pair
from oidc_test import __version__
from oidc_test.registry import registry
from oidc_test.auth.provider import NoPublicKey, Provider
from oidc_test.auth.utils import (
@ -109,7 +108,6 @@ async def home(
"show_token": settings.show_token,
"user": user,
"now": datetime.now(),
"__version__": __version__,
}
if provider is None or token is None:
context["providers"] = providers
@ -125,20 +123,19 @@ async def home(
try:
access_token_parsed = provider.decode(token["access_token"], verify_signature=False)
context["access_token_parsed"] = access_token_parsed
context["access_token_scope"] = access_token_parsed.get("scope")
except PyJWTError as err:
context["access_token_parsed"] = {"Cannot parse": err.__class__.__name__}
context["access_token_scope"] = None
access_token_parsed = {"Cannot parse": err.__class__.__name__}
try:
id_token_parsed = provider.decode(token["id_token"], verify_signature=False)
context["id_token_parsed"] = id_token_parsed
except PyJWTError as err:
context["id_token_parsed"] = {"Cannot parse": err.__class__.__name__}
id_token_parsed = {"Cannot parse": err.__class__.__name__}
try:
refresh_token_parsed = provider.decode(token["refresh_token"], verify_signature=False)
context["refresh_token_parsed"] = refresh_token_parsed
except PyJWTError as err:
context["refresh_token_parsed"] = {"Cannot parse": err.__class__.__name__}
refresh_token_parsed = {"Cannot parse": err.__class__.__name__}
context["access_token_scope"] = access_token_parsed.get("scope")
context["resources"] = registry.resources
context["resource_providers"] = provider.resource_providers
return templates.TemplateResponse(name="home.html", request=request, context=context)

View file

@ -21,12 +21,6 @@ hr {
.hidden {
display: none;
}
.version {
position: absolute;
font-size: 75%;
top: 0.3em;
right: 0.3em;
}
.center {
text-align: center;
}

View file

@ -5,7 +5,6 @@
<script src="{{ url_for('static', path='/utils.js') }}"></script>
</head>
<body onload="checkPerms('links-to-check', '{{ access_token }}', '{{ auth_provider.id }}')">
<div class="version">v. {{ __version__}}</div>
<h1>OIDC-test - FastAPI client</h1>
{% block content %}
{% endblock %}

View file

@ -99,7 +99,7 @@
{{ resource.name }}
</button>
{% else %}
<button resource-name="{{ resource.resource_name }}"
<button resource-name="{{ resource.name }}"
onclick="get_resource('{{ resource.resource_name }}', '{{ access_token }}', '{{ auth_provider.id }}')"
>
{{ resource.name }}

16
uv.lock generated
View file

@ -1,5 +1,4 @@
version = 1
revision = 1
requires-python = ">=3.13"
[[package]]
@ -207,18 +206,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632 },
]
[[package]]
name = "dunamai"
version = "1.23.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/4e/a5c8c337a1d9ac0384298ade02d322741fb5998041a5ea74d1cd2a4a1d47/dunamai-1.23.0.tar.gz", hash = "sha256:a163746de7ea5acb6dacdab3a6ad621ebc612ed1e528aaa8beedb8887fccd2c4", size = 44681 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/21/4c/963169386309fec4f96fd61210ac0a0666887d0fb0a50205395674d20b71/dunamai-1.23.0-py3-none-any.whl", hash = "sha256:a0906d876e92441793c6a423e16a4802752e723e9c9a5aabdc5535df02dbe041", size = 26342 },
]
[[package]]
name = "ecdsa"
version = "0.19.0"
@ -495,6 +482,7 @@ wheels = [
[[package]]
name = "oidc-fastapi-test"
version = "0.0.0"
source = { editable = "." }
dependencies = [
{ name = "authlib" },
@ -513,7 +501,6 @@ dependencies = [
[package.dev-dependencies]
dev = [
{ name = "dunamai" },
{ name = "ipdb" },
{ name = "pytest" },
]
@ -536,7 +523,6 @@ requires-dist = [
[package.metadata.requires-dev]
dev = [
{ name = "dunamai", specifier = ">=1.23.0" },
{ name = "ipdb", specifier = ">=0.13.13" },
{ name = "pytest", specifier = ">=8.3.4" },
]