Many updates, too log to list
This commit is contained in:
parent
5c3d54c3f2
commit
23f180e521
6 changed files with 0 additions and 0 deletions
145
src/oidc_test/main.py
Normal file
145
src/oidc_test/main.py
Normal 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>")
|
Loading…
Add table
Add a link
Reference in a new issue