Many updates, too log to list

This commit is contained in:
phil 2025-01-09 19:39:20 +01:00
parent 5c3d54c3f2
commit 23f180e521
6 changed files with 0 additions and 0 deletions

145
src/oidc_test/main.py Normal file
View file

@ -0,0 +1,145 @@
from typing import Annotated
from httpx import HTTPError
from fastapi import Depends, FastAPI, HTTPException, Request, status
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from starlette.middleware.sessions import SessionMiddleware
from authlib.integrations.starlette_client.apps import StarletteOAuth2App
from authlib.integrations.starlette_client import OAuth, OAuthError
from .settings import settings
from .models import User
from .auth_utils import hasrole, get_current_user_or_none, get_current_user
from .database import db
templates = Jinja2Templates("src/templates")
app = FastAPI(
title="OIDC auth test",
)
# SessionMiddleware is required by authlib
app.add_middleware(
SessionMiddleware,
secret_key=settings.secret_key,
)
# Add oidc providers to authlib from the settings
authlib_oauth = OAuth()
for provider in settings.oidc.providers:
authlib_oauth.register(
name=provider.name,
server_metadata_url=provider.provider_url,
client_kwargs={
"scope": "openid email offline_access profile roles",
},
client_id=provider.client_id,
client_secret=provider.client_secret,
# client_id="some-client-id", # if enabled, authlib will also check that the access token belongs to this client id (audience)
)
@app.get("/login")
async def login(request: Request, provider: str) -> RedirectResponse:
redirect_uri = request.url_for("auth", oidc_provider_id=provider)
try:
provider_: StarletteOAuth2App = getattr(authlib_oauth, provider)
except AttributeError:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "No such provider")
try:
return await provider_.authorize_redirect(request, redirect_uri)
except HTTPError:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Cannot reach provider")
@app.get("/auth/{oidc_provider_id}")
async def auth(request: Request, oidc_provider_id: str) -> RedirectResponse:
try:
oidc_provider: StarletteOAuth2App = getattr(authlib_oauth, oidc_provider_id)
except AttributeError:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "No such provider")
try:
token = await oidc_provider.authorize_access_token(request)
except OAuthError as error:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=error.error)
# Remember the oidc_provider in the session
request.session["oidc_provider_id"] = oidc_provider_id
#
# One could process the full decoded token which contains extra information
# eg for updates. Here we are only interested in roles
#
if userinfo := token.get("userinfo"):
# sub given by oidc provider
sub = userinfo["sub"]
# Build and remember the user in the session
request.session["user_sub"] = sub
# Store the user in the database
await db.add_user(sub, user_info=userinfo, oidc_provider=oidc_provider)
return RedirectResponse(url="/")
else:
# Not sure if it's correct to redirect to plain login (which is not implemented anyway)
# if no userinfo is provided
return RedirectResponse(url="/login")
@app.get("/logout")
async def logout(
request: Request, user: Annotated[User, Depends(get_current_user_or_none)]
) -> RedirectResponse:
# TODO: logout from oidc_provider
# await user.oidc_provider.logout_redirect()
request.session.pop("user_sub", None)
return RedirectResponse(url="/")
@app.get("/")
async def home(
request: Request, user: Annotated[User, Depends(get_current_user_or_none)]
) -> HTMLResponse:
return templates.TemplateResponse(
request=request,
context={
"settings": settings.model_dump(),
"user": user,
},
name="index.html",
)
@app.get("/public")
async def public() -> HTMLResponse:
return HTMLResponse("<h1>Not protected</h1>")
@app.get("/protected")
async def get_protected(
user: Annotated[User, Depends(get_current_user)]
) -> HTMLResponse:
return HTMLResponse("<h1>Only authenticated users can see this</h1>")
@app.get("/protected-by-foorole")
@hasrole("foorole")
async def get_protected_by_foorole(request: Request) -> HTMLResponse:
return HTMLResponse("<h1>Only users with foorole can see this</h1>")
@app.get("/protected-by-barrole")
@hasrole("barrole")
async def get_protected_by_barrole(request: Request) -> HTMLResponse:
return HTMLResponse("<h1>Protected by barrole</h1>")
@app.get("/protected-by-foorole-and-barrole")
@hasrole("barrole")
@hasrole("foorole")
async def get_protected_by_foorole_and_barrole(request: Request) -> HTMLResponse:
return HTMLResponse("<h1>Only users with foorole and barrole can see this</h1>")
@app.get("/protected-by-foorole-or-barrole")
@hasrole(["foorole", "barrole"])
async def get_protected_by_foorole_or_barrole(request: Request) -> HTMLResponse:
return HTMLResponse("<h1>Only users with foorole or barrole can see this</h1>")