Store raw access token within user; get resource
Some checks failed
/ build (push) Failing after 15s
/ test (push) Successful in 5s

This commit is contained in:
phil 2025-02-03 13:20:33 +01:00
parent e1dac77738
commit dc181bd3a8
7 changed files with 94 additions and 30 deletions

View file

@ -25,8 +25,8 @@ oidc_providers_settings: dict[str, OIDCProvider] = dict(
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def fetch_token(name, request): async def fetch_token(name, request):
breakpoint() logger.warn("TODO: fetch_token")
... ...
# if name in oidc_providers: # if name in oidc_providers:
# model = OAuth2Token # model = OAuth2Token
@ -37,8 +37,8 @@ def fetch_token(name, request):
# return token.to_token() # return token.to_token()
def update_token(*args, **kwargs): async def update_token(*args, **kwargs):
breakpoint() logger.warn("TODO: update_token")
... ...
@ -211,7 +211,8 @@ async def get_user_from_token(
) )
try: try:
user = await db.get_user(user_id) user = await db.get_user(user_id)
user.access_token = payload if user.access_token != token:
user.access_token = token
except UserNotInDB: except UserNotInDB:
logger.info( logger.info(
f"User {user_id} not found in DB, creating it (real apps can behave differently" f"User {user_id} not found in DB, creating it (real apps can behave differently"
@ -221,6 +222,6 @@ async def get_user_from_token(
user_info=payload, user_info=payload,
oidc_provider=getattr(authlib_oauth, auth_provider_id), oidc_provider=getattr(authlib_oauth, auth_provider_id),
user_info_from_endpoint={}, user_info_from_endpoint={},
access_token=payload, access_token=token,
) )
return user return user

View file

@ -30,7 +30,7 @@ class Database:
user_info: dict, user_info: dict,
oidc_provider: StarletteOAuth2App, oidc_provider: StarletteOAuth2App,
user_info_from_endpoint: dict, user_info_from_endpoint: dict,
access_token: dict, access_token: str,
) -> User: ) -> User:
user = User.from_auth(userinfo=user_info, oidc_provider=oidc_provider) user = User.from_auth(userinfo=user_info, oidc_provider=oidc_provider)
user.access_token = access_token user.access_token = access_token

View file

@ -200,7 +200,7 @@ async def auth(request: Request, oidc_provider_id: str) -> RedirectResponse:
user_info=userinfo, user_info=userinfo,
oidc_provider=oidc_provider, oidc_provider=oidc_provider,
user_info_from_endpoint=user_info_from_endpoint, user_info_from_endpoint=user_info_from_endpoint,
access_token=access_token, access_token=token["access_token"],
) )
# Add the id_token to the session # Add the id_token to the session
request.session["token"] = token["id_token"] request.session["token"] = token["id_token"]
@ -229,7 +229,7 @@ async def account(
raise HTTPException( raise HTTPException(
status.HTTP_406_NOT_ACCEPTABLE, detail="No oidc provider settings" status.HTTP_406_NOT_ACCEPTABLE, detail="No oidc provider settings"
) )
return RedirectResponse(f"{oidc_provider_settings.account_url}") return RedirectResponse(f"{oidc_provider_settings.account_url_template}")
@app.get("/logout") @app.get("/logout")
@ -243,7 +243,9 @@ async def logout(
if ( if (
provider_logout_uri := oidc_provider.server_metadata.get("end_session_endpoint") provider_logout_uri := oidc_provider.server_metadata.get("end_session_endpoint")
) is None: ) is None:
logger.warn(f"Cannot find end_session_endpoint for provider {provider.name}") logger.warn(
f"Cannot find end_session_endpoint for provider {oidc_provider.name}"
)
return RedirectResponse(request.url_for("non_compliant_logout")) return RedirectResponse(request.url_for("non_compliant_logout"))
post_logout_uri = request.url_for("home") post_logout_uri = request.url_for("home")
if (token := await db.get_token(request.session.pop("token", None))) is None: if (token := await db.get_token(request.session.pop("token", None))) is None:

View file

@ -25,14 +25,14 @@ class UserBase(SQLModel, extra="ignore"):
class User(UserBase): class User(UserBase):
model_config = ConfigDict(arbitrary_types_allowed=True) model_config = ConfigDict(arbitrary_types_allowed=True) # type:ignore
sub: str = Field( sub: str = Field(
description="""subject id of the user given by the oidc provider, description="""subject id of the user given by the oidc provider,
also the key for the database 'table'""", also the key for the database 'table'""",
) )
userinfo: dict = {} userinfo: dict = {}
access_token: dict = {} access_token: str | None = None
oidc_provider: StarletteOAuth2App | None = None oidc_provider: StarletteOAuth2App | None = None
@classmethod @classmethod
@ -54,5 +54,15 @@ class User(UserBase):
def has_scope(self, scope: str) -> bool: def has_scope(self, scope: str) -> bool:
"""Check if the scope is present in user info or access token""" """Check if the scope is present in user info or access token"""
info_scopes = self.userinfo.get("scope", "").split(" ") info_scopes = self.userinfo.get("scope", "").split(" ")
access_token_scopes = self.access_token.get("scope", "").split(" ") access_token_scopes = self.access_token_parsed().get("scope", "").split(" ")
return scope in set(info_scopes + access_token_scopes) return scope in set(info_scopes + access_token_scopes)
def access_token_parsed(self):
assert self.access_token is not None
assert self.oidc_provider is not None
assert self.oidc_provider.name is not None
from .auth_utils import oidc_providers_settings
return oidc_providers_settings[self.oidc_provider.name].decode(
self.access_token
)

View file

@ -3,9 +3,9 @@ body {
background-color: floralwhite; background-color: floralwhite;
margin: 0; margin: 0;
font-family: system-ui; font-family: system-ui;
text-align: center;
} }
h1 { h1 {
text-align: center;
background-color: #f7c7867d; background-color: #f7c7867d;
margin: 0 0 0.2em 0; margin: 0 0 0.2em 0;
box-shadow: 0px 0.2em 0.2em #f7c7867d; box-shadow: 0px 0.2em 0.2em #f7c7867d;
@ -21,9 +21,6 @@ hr {
.hidden { .hidden {
display: none; display: none;
} }
.center {
text-align: center;
}
.content { .content {
width: 100%; width: 100%;
display: flex; display: flex;
@ -55,7 +52,6 @@ hr {
border: 2px solid darkkhaki; border: 2px solid darkkhaki;
padding: 3px 6px; padding: 3px 6px;
text-decoration: none; text-decoration: none;
text-align: center;
color: black; color: black;
} }
.user-info a.logout:hover { .user-info a.logout:hover {
@ -70,7 +66,6 @@ hr {
margin: 0; margin: 0;
} }
.debug-auth p { .debug-auth p {
text-align: center;
border-bottom: 1px solid black; border-bottom: 1px solid black;
} }
.debug-auth ul { .debug-auth ul {
@ -101,16 +96,24 @@ hr {
.hasResponseStatus.status-503 { .hasResponseStatus.status-503 {
background-color: #ffA88050; background-color: #ffA88050;
} }
.role {
.role, .scope {
padding: 3px 6px; padding: 3px 6px;
background-color: #44228840; margin: 3px;
border-radius: 6px; border-radius: 6px;
} }
.role {
background-color: #44228840;
}
.scope {
background-color: #8888FF80;
}
/* For home */ /* For home */
.login-box { .login-box {
text-align: center;
background-color: antiquewhite; background-color: antiquewhite;
margin: 0.5em auto; margin: 0.5em auto;
width: fit-content; width: fit-content;
@ -137,7 +140,6 @@ hr {
max-height: 2em; max-height: 2em;
} }
.providers .provider .link div { .providers .provider .link div {
text-align: center;
background-color: #f7c7867d; background-color: #f7c7867d;
border-radius: 8px; border-radius: 8px;
padding: 6px; padding: 6px;
@ -152,13 +154,11 @@ hr {
} }
.providers .error { .providers .error {
padding: 3px 6px; padding: 3px 6px;
text-align: center;
font-weight: bold; font-weight: bold;
flex: 1 1 auto; flex: 1 1 auto;
} }
.content .links-to-check { .content .links-to-check {
display: flex; display: flex;
text-align: center;
justify-content: center; justify-content: center;
gap: 0.5em; gap: 0.5em;
flex-flow: wrap; flex-flow: wrap;

View file

@ -17,3 +17,24 @@ function checkPerms(className) {
Array.from(elem.children).forEach(elem => checkHref(elem)) Array.from(elem.children).forEach(elem => checkHref(elem))
) )
} }
async function get_resource(id, token, authProvider) {
//if (!keycloak.keycloak) { return }
const resp = await fetch("resource/" + id, {
method: "GET",
headers: new Headers({
"Content-type": "application/json",
"Authorization": `Bearer ${token}`,
"auth_provider": authProvider,
}),
})
/*
resource.value = resp['data']
msg.value = ""
}
).catch (
err => msg.value = err
)
*/
console.log(await resp.json())
}

View file

@ -30,6 +30,10 @@
<img src="{{ user.picture }}" class="picture"></img> <img src="{{ user.picture }}" class="picture"></img>
{% endif %} {% endif %}
<div>{{ user.email }}</div> <div>{{ user.email }}</div>
<div>
<span>Provider:</span>
{{ oidc_provider_settings.name }}
</div>
{% if user.roles %} {% if user.roles %}
<div> <div>
<span>Roles:</span> <span>Roles:</span>
@ -38,17 +42,43 @@
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
<div> {% if user.access_token.scope %}
<span>Provider:</span> <div>
{{ oidc_provider_settings.name }} <span>Scopes</span>:
</div> {% for scope in user.access_token.scope.split(' ') %}
<span class="scope">{{ scope }}</span>
{% endfor %}
</div>
{% endif %}
{% if oidc_provider_settings.account_url_template %} {% if oidc_provider_settings.account_url_template %}
<button onclick="location.href='{{ oidc_provider_settings.get_account_url(request, user) }}'" class="account">Account management</button> <button
onclick="location.href='{{ oidc_provider_settings.get_account_url(request, user) }}'"
class="account">
Account management
</button>
{% endif %} {% endif %}
<button onclick="location.href='{{ request.url_for("logout") }}'" class="logout">Logout</button> <button onclick="location.href='{{ request.url_for("logout") }}'" class="logout">Logout</button>
</div> </div>
{% endif %} {% endif %}
<hr> <hr>
<p class="center">
Fetch resources from the resource server with your authentication token:
</p>
<div class="actions">
<button onclick="get_resource('time', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">Time</button>
<button onclick="get_resource('bs', '{{ user.access_token }}', '{{ oidc_provider_settings.id }}')">BS</button>
</div>
<div class="resources">
<div v-if="Object.entries(resource).length > 0" class="resource">
<div v-for="(value, key) in resource">
<div class="key">{{ key }}</div>
<div v-if="key == 'sorry' || key == 'error'" class="error">{{ value }}</div>
<div v-else class="value">{{ value }}</div>
</div>
</div>
</div>
<div v-if="msg" class="msg resource">{{ msg }}</div>
<hr>
<div class="content"> <div class="content">
<p> <p>
These links should get different response codes depending on the authorization: These links should get different response codes depending on the authorization: