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")
def fetch_token(name, request):
breakpoint()
async def fetch_token(name, request):
logger.warn("TODO: fetch_token")
...
# if name in oidc_providers:
# model = OAuth2Token
@ -37,8 +37,8 @@ def fetch_token(name, request):
# return token.to_token()
def update_token(*args, **kwargs):
breakpoint()
async def update_token(*args, **kwargs):
logger.warn("TODO: update_token")
...
@ -211,7 +211,8 @@ async def get_user_from_token(
)
try:
user = await db.get_user(user_id)
user.access_token = payload
if user.access_token != token:
user.access_token = token
except UserNotInDB:
logger.info(
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,
oidc_provider=getattr(authlib_oauth, auth_provider_id),
user_info_from_endpoint={},
access_token=payload,
access_token=token,
)
return user

View file

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

View file

@ -200,7 +200,7 @@ async def auth(request: Request, oidc_provider_id: str) -> RedirectResponse:
user_info=userinfo,
oidc_provider=oidc_provider,
user_info_from_endpoint=user_info_from_endpoint,
access_token=access_token,
access_token=token["access_token"],
)
# Add the id_token to the session
request.session["token"] = token["id_token"]
@ -229,7 +229,7 @@ async def account(
raise HTTPException(
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")
@ -243,7 +243,9 @@ async def logout(
if (
provider_logout_uri := oidc_provider.server_metadata.get("end_session_endpoint")
) 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"))
post_logout_uri = request.url_for("home")
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):
model_config = ConfigDict(arbitrary_types_allowed=True)
model_config = ConfigDict(arbitrary_types_allowed=True) # type:ignore
sub: str = Field(
description="""subject id of the user given by the oidc provider,
also the key for the database 'table'""",
)
userinfo: dict = {}
access_token: dict = {}
access_token: str | None = None
oidc_provider: StarletteOAuth2App | None = None
@classmethod
@ -54,5 +54,15 @@ class User(UserBase):
def has_scope(self, scope: str) -> bool:
"""Check if the scope is present in user info or access token"""
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)
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;
margin: 0;
font-family: system-ui;
text-align: center;
}
h1 {
text-align: center;
background-color: #f7c7867d;
margin: 0 0 0.2em 0;
box-shadow: 0px 0.2em 0.2em #f7c7867d;
@ -21,9 +21,6 @@ hr {
.hidden {
display: none;
}
.center {
text-align: center;
}
.content {
width: 100%;
display: flex;
@ -55,7 +52,6 @@ hr {
border: 2px solid darkkhaki;
padding: 3px 6px;
text-decoration: none;
text-align: center;
color: black;
}
.user-info a.logout:hover {
@ -70,7 +66,6 @@ hr {
margin: 0;
}
.debug-auth p {
text-align: center;
border-bottom: 1px solid black;
}
.debug-auth ul {
@ -101,16 +96,24 @@ hr {
.hasResponseStatus.status-503 {
background-color: #ffA88050;
}
.role {
.role, .scope {
padding: 3px 6px;
background-color: #44228840;
margin: 3px;
border-radius: 6px;
}
.role {
background-color: #44228840;
}
.scope {
background-color: #8888FF80;
}
/* For home */
.login-box {
text-align: center;
background-color: antiquewhite;
margin: 0.5em auto;
width: fit-content;
@ -137,7 +140,6 @@ hr {
max-height: 2em;
}
.providers .provider .link div {
text-align: center;
background-color: #f7c7867d;
border-radius: 8px;
padding: 6px;
@ -152,13 +154,11 @@ hr {
}
.providers .error {
padding: 3px 6px;
text-align: center;
font-weight: bold;
flex: 1 1 auto;
}
.content .links-to-check {
display: flex;
text-align: center;
justify-content: center;
gap: 0.5em;
flex-flow: wrap;

View file

@ -17,3 +17,24 @@ function checkPerms(className) {
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>
{% endif %}
<div>{{ user.email }}</div>
<div>
<span>Provider:</span>
{{ oidc_provider_settings.name }}
</div>
{% if user.roles %}
<div>
<span>Roles:</span>
@ -38,17 +42,43 @@
{% endfor %}
</div>
{% endif %}
<div>
<span>Provider:</span>
{{ oidc_provider_settings.name }}
</div>
{% if user.access_token.scope %}
<div>
<span>Scopes</span>:
{% for scope in user.access_token.scope.split(' ') %}
<span class="scope">{{ scope }}</span>
{% endfor %}
</div>
{% endif %}
{% 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 %}
<button onclick="location.href='{{ request.url_for("logout") }}'" class="logout">Logout</button>
</div>
{% endif %}
<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">
<p>
These links should get different response codes depending on the authorization: