These links should get different response codes depending on the authorization:
From af49242192c29a38b58819ebd0f2e8ae55bd87b6 Mon Sep 17 00:00:00 2001
From: phil
From 90cfdb66dd28c0e2efc5fbde4ba31e41f09e8ae7 Mon Sep 17 00:00:00 2001
From: phil Only authenticated users can see this
")
@app.get("/protected-by-foorole")
@hasrole("foorole")
async def get_protected_by_foorole(request: Request) -> HTMLResponse:
+ assert request is not None
return HTMLResponse("Only users with foorole can see this
")
@app.get("/protected-by-barrole")
@hasrole("barrole")
async def get_protected_by_barrole(request: Request) -> HTMLResponse:
+ assert request is not None
return HTMLResponse("Protected by barrole
")
@@ -318,12 +331,14 @@ async def get_protected_by_barrole(request: Request) -> HTMLResponse:
@hasrole("barrole")
@hasrole("foorole")
async def get_protected_by_foorole_and_barrole(request: Request) -> HTMLResponse:
+ assert request is not None
return HTMLResponse("Only users with foorole and barrole can see this
")
@app.get("/protected-by-foorole-or-barrole")
@hasrole(["foorole", "barrole"])
async def get_protected_by_foorole_or_barrole(request: Request) -> HTMLResponse:
+ assert request is not None
return HTMLResponse("Only users with foorole or barrole can see this
")
@@ -333,6 +348,7 @@ async def get_introspect(
oidc_provider: Annotated[StarletteOAuth2App, Depends(get_oidc_provider)],
token: Annotated[OAuth2Token, Depends(get_token)],
) -> JSONResponse:
+ assert request is not None
if (
response := await oidc_provider.post(
oidc_provider.server_metadata["introspection_endpoint"],
@@ -352,6 +368,7 @@ async def get_forgejo_user_info(
oidc_provider: Annotated[StarletteOAuth2App, Depends(get_oidc_provider)],
token: Annotated[OAuth2Token, Depends(get_token)],
) -> HTMLResponse:
+ assert request is not None
if (
response := await oidc_provider.get(
"/api/v1/user/repos",
diff --git a/src/oidc_test/templates/home.html b/src/oidc_test/templates/home.html
index ab4dc77..ac732e4 100644
--- a/src/oidc_test/templates/home.html
+++ b/src/oidc_test/templates/home.html
@@ -58,8 +58,6 @@
Auth + barrole protected content
Auth + foorole and barrole protected content
Using FastAPI Depends
- Other
- OAuth2 test (forgejo user info)
Introspect token (401 expected)
{% if resources %}
From 5f2901d55896d9a191e58c7ca161c2304a684daa Mon Sep 17 00:00:00 2001
From: phil Only authenticated users can see this
")
@app.get("/protected-by-foorole")
@hasrole("foorole")
async def get_protected_by_foorole(request: Request) -> HTMLResponse:
- assert request is not None
+ assert request is not None # Just to keep QA checks happy
return HTMLResponse("Only users with foorole can see this
")
@app.get("/protected-by-barrole")
@hasrole("barrole")
async def get_protected_by_barrole(request: Request) -> HTMLResponse:
- assert request is not None
+ assert request is not None # Just to keep QA checks happy
return HTMLResponse("Protected by barrole
")
@@ -331,14 +333,14 @@ async def get_protected_by_barrole(request: Request) -> HTMLResponse:
@hasrole("barrole")
@hasrole("foorole")
async def get_protected_by_foorole_and_barrole(request: Request) -> HTMLResponse:
- assert request is not None
+ assert request is not None # Just to keep QA checks happy
return HTMLResponse("Only users with foorole and barrole can see this
")
@app.get("/protected-by-foorole-or-barrole")
@hasrole(["foorole", "barrole"])
async def get_protected_by_foorole_or_barrole(request: Request) -> HTMLResponse:
- assert request is not None
+ assert request is not None # Just to keep QA checks happy
return HTMLResponse("Only users with foorole or barrole can see this
")
@@ -348,7 +350,7 @@ async def get_introspect(
oidc_provider: Annotated[StarletteOAuth2App, Depends(get_oidc_provider)],
token: Annotated[OAuth2Token, Depends(get_token)],
) -> JSONResponse:
- assert request is not None
+ assert request is not None # Just to keep QA checks happy
if (
response := await oidc_provider.post(
oidc_provider.server_metadata["introspection_endpoint"],
@@ -361,31 +363,10 @@ async def get_introspect(
raise HTTPException(status_code=response.status_code, detail=response.text)
-@app.get("/oauth2-forgejo-test")
-async def get_forgejo_user_info(
- request: Request,
- user: Annotated[User, Depends(get_current_user)],
- oidc_provider: Annotated[StarletteOAuth2App, Depends(get_oidc_provider)],
- token: Annotated[OAuth2Token, Depends(get_token)],
-) -> HTMLResponse:
- assert request is not None
- if (
- response := await oidc_provider.get(
- "/api/v1/user/repos",
- # headers={"Authorization": f"token {token['access_token']}"},
- token=token,
- )
- ).is_success:
- repos = response.json()
- names = [repo["name"] for repo in repos]
- return HTMLResponse(f"{user.name} has {len(repos)} repos: {', '.join(names)}")
- else:
- raise HTTPException(status_code=response.status_code, detail=response.text)
-
-
# Snippet for running standalone
# Mostly useful for the --version option,
-# as running with uvicorn is easy and provides flaxibility
+# as running with uvicorn is easy and provides better flexibility, eg.
+# uvicorn --host foo oidc_test.main:app --reload
def main():
From 572d2a7b0d0973ec7fa56efdbc68cb9838dad74e Mon Sep 17 00:00:00 2001
From: phil
@@ -66,7 +69,7 @@
+ Fetch resources from the resource server with your authentication token: +
+
These links should get different response codes depending on the authorization:
From af49242192c29a38b58819ebd0f2e8ae55bd87b6 Mon Sep 17 00:00:00 2001
From: phil
Fetch resources from the resource server with your authentication token:
@@ -68,17 +69,9 @@ -
These links should get different response codes depending on the authorization:
From fefe44acfef0a16c492070ac8bc9f43d4637aa2c Mon Sep 17 00:00:00 2001
From: phil
From 3eb6dc3dcf4be7350aa4cda6b3157183fe77d5d8 Mon Sep 17 00:00:00 2001
From: phil Not protected
")
+async def public() -> dict:
+ return {"msg": "Not protected"}
@resource_server.get("/protected")
-async def get_protected(user: Annotated[User, Depends(get_user_from_token)]) -> HTMLResponse:
+async def get_protected(user: Annotated[User, Depends(get_user_from_token)]):
assert user is not None # Just to keep QA checks happy
- return HTMLResponse("Only authenticated users can see this
")
+ return {"msg": "Only authenticated users can see this"}
@resource_server.get("/protected-by-foorole")
async def get_protected_by_foorole(
- user: Annotated[User, Depends(UserWithRole("foorole"))]
-) -> HTMLResponse:
- return HTMLResponse("Only users with foorole can see this
")
+ user: Annotated[User, Depends(UserWithRole("foorole"))],
+):
+ assert user is not None
+ return {"msg": "Only users with foorole can see this"}
@resource_server.get("/protected-by-barrole")
async def get_protected_by_barrole(
- user: Annotated[User, Depends(UserWithRole("barrole"))]
-) -> HTMLResponse:
- return HTMLResponse("Protected by barrole
")
+ user: Annotated[User, Depends(UserWithRole("barrole"))],
+):
+ assert user is not None
+ return {"msg": "Protected by barrole"}
@resource_server.get("/protected-by-foorole-and-barrole")
async def get_protected_by_foorole_and_barrole(
user: Annotated[User, Depends(UserWithRole("foorole")), Depends(UserWithRole("barrole"))],
-) -> HTMLResponse:
+):
assert user is not None # Just to keep QA checks happy
- return HTMLResponse("Only users with foorole and barrole can see this
")
+ return {"msg": "Only users with foorole and barrole can see this"}
@resource_server.get("/protected-by-foorole-or-barrole")
async def get_protected_by_foorole_or_barrole(
- user: Annotated[User, Depends(UserWithRole(["foorole", "barrole"]))]
-) -> HTMLResponse:
+ user: Annotated[User, Depends(UserWithRole(["foorole", "barrole"]))],
+):
assert user is not None # Just to keep QA checks happy
- return HTMLResponse("Only users with foorole or barrole can see this
")
+ return {"msg": "Only users with foorole or barrole can see this"}
# @resource_server.get("/introspect")
@@ -118,9 +119,9 @@ async def get_resource_(
# oidc_provider: Annotated[StarletteOAuth2App, Depends(get_oidc_provider)],
# token: Annotated[OAuth2Token, Depends(get_token)],
user: Annotated[User, Depends(get_user_from_token)],
-) -> JSONResponse:
+):
"""Generic path for testing a resource provided by a provider"""
- return JSONResponse(await get_resource(id, user))
+ return await get_resource(id, user)
async def get_resource(resource_id: str, user: User) -> dict:
diff --git a/src/oidc_test/settings.py b/src/oidc_test/settings.py
index 2544bd7..b601739 100644
--- a/src/oidc_test/settings.py
+++ b/src/oidc_test/settings.py
@@ -43,9 +43,7 @@ class OIDCProvider(BaseModel):
info_url: str | None = (
None # Used eg. for Keycloak's public key (see https://stackoverflow.com/questions/54318633/getting-keycloaks-public-key)
)
- info: dict[str, str | int] | None = (
- None # Info fetched from info_url, eg. public key
- )
+ info: dict[str, str | int] | None = None # Info fetched from info_url, eg. public key
public_key: str | None = None
signature_alg: str = "RS256"
resource_provider_scopes: list[str] = []
@@ -62,25 +60,17 @@ class OIDCProvider(BaseModel):
def get_account_url(self, request: Request, user: User) -> str | None:
if self.account_url_template:
- if not (
- self.url.endswith("/") or self.account_url_template.startswith("/")
- ):
+ if not (self.url.endswith("/") or self.account_url_template.startswith("/")):
sep = "/"
else:
sep = ""
- return (
- self.url
- + sep
- + self.account_url_template.format(request=request, user=user)
- )
+ return self.url + sep + self.account_url_template.format(request=request, user=user)
else:
return None
def get_public_key(self) -> str:
"""Return the public key formatted for decoding token"""
- public_key = self.public_key or (
- self.info is not None and self.info["public_key"]
- )
+ public_key = self.public_key or (self.info is not None and self.info["public_key"])
if public_key is None:
raise AttributeError(f"Cannot get public key for {self.name}")
return f"""
@@ -91,17 +81,18 @@ class OIDCProvider(BaseModel):
def decode(self, token: str, verify_signature: bool = True) -> dict[str, Any]:
"""Decode the token with signature check"""
- decoded = decode(
- token,
- self.get_public_key(),
- algorithms=[self.signature_alg],
- audience=["account", "oidc-test", "oidc-test-web"],
- options={
- "verify_signature": False,
- "verify_aud": False,
- }, # not settings.insecure.skip_verify_signature},
- )
- logger.debug(str(decoded))
+ if settings.debug_token:
+ decoded = decode(
+ token,
+ self.get_public_key(),
+ algorithms=[self.signature_alg],
+ audience=["account", "oidc-test", "oidc-test-web"],
+ options={
+ "verify_signature": False,
+ "verify_aud": False,
+ }, # not settings.insecure.skip_verify_signature},
+ )
+ logger.debug(str(decoded))
return decode(
token,
self.get_public_key(),
@@ -143,6 +134,7 @@ class Settings(BaseSettings):
log: bool = False
insecure: Insecure = Insecure()
cors_origins: list[str] = []
+ debug_token: bool = False
@classmethod
def settings_customise_sources(
@@ -161,9 +153,7 @@ class Settings(BaseSettings):
settings_cls,
Path(
Path(
- environ.get(
- "OIDC_TEST_SETTINGS_FILE", Path.cwd() / "settings.yaml"
- ),
+ environ.get("OIDC_TEST_SETTINGS_FILE", Path.cwd() / "settings.yaml"),
)
),
),
diff --git a/src/oidc_test/static/styles.css b/src/oidc_test/static/styles.css
index 7e1260b..6262d79 100644
--- a/src/oidc_test/static/styles.css
+++ b/src/oidc_test/static/styles.css
@@ -171,11 +171,13 @@ hr {
gap: 0.5em;
flex-flow: wrap;
}
-.content .links-to-check a {
+.content .links-to-check button {
color: black;
padding: 5px 10px;
text-decoration: none;
border-radius: 8px;
+ border: none;
+ cursor: pointer;
}
.token {
@@ -183,12 +185,6 @@ hr {
font-family: monospace;
}
-.actions {
- display: flex;
- justify-content: center;
- gap: 0.5em;
-}
-
.resourceResult {
padding: 0.5em;
gap: 0.5em;
diff --git a/src/oidc_test/static/utils.js b/src/oidc_test/static/utils.js
index e6c4bfc..6ea8da2 100644
--- a/src/oidc_test/static/utils.js
+++ b/src/oidc_test/static/utils.js
@@ -1,6 +1,7 @@
async function checkHref(elem, token, authProvider) {
const msg = document.getElementById("msg")
- const resp = await fetch(elem.href, {
+ const url = `resource/${elem.getAttribute("resource-id")}`
+ const resp = await fetch(url, {
headers: new Headers({
"Content-type": "application/json",
"Authorization": `Bearer ${token}`,
diff --git a/src/oidc_test/templates/home.html b/src/oidc_test/templates/home.html
index 09c313f..92b7068 100644
--- a/src/oidc_test/templates/home.html
+++ b/src/oidc_test/templates/home.html
@@ -61,33 +61,30 @@
- Fetch resources from the resource server with your authentication token: -
-- These links should get different response codes depending on the authorization: +
+ Resources validated by scope:
+ Resources validated by role: +
+
From ff72f0cae585858e400a9bc8f7d3fe1727035c44 Mon Sep 17 00:00:00 2001
From: phil
User info
-Resources validated by role:
Log in with:
{{ provider.name }}
@@ -32,7 +32,7 @@
{{ user.email }}
Provider:
- {{ oidc_provider_settings.name }}
+ {{ auth_provider.name }}
{% if user.roles %}
@@ -50,9 +50,9 @@
{% endfor %}
{% endif %}
- {% if oidc_provider_settings.account_url_template %}
+ {% if auth_provider.account_url_template %}
@@ -67,21 +67,21 @@
Resources validated by scope:
-
-
+
+
Resources validated by role:
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
From 496ce016e3d31fdb003146a3f84bc86726275e6d Mon Sep 17 00:00:00 2001
From: phil
- {% if settings.show_token and id_token_parsed %}
+ {% if show_token and id_token_parsed %}
-
- {% endif %}
- {% if user %}
+ Log in with: -
+
+ {% else %}
- Log in with: +
-
-
-
{% if resources %}
Resources for this provider:
{% for resource in resources %}
- {{ resource.name }}
+
{% endif %}
+
+
+
+
diff --git a/src/oidc_test/templates/non_compliant_logout.html b/src/oidc_test/templates/non_compliant_logout.html
index 24a96ae..56758de 100644
--- a/src/oidc_test/templates/non_compliant_logout.html
+++ b/src/oidc_test/templates/non_compliant_logout.html
@@ -6,12 +6,12 @@
authorisation to log in again without asking for credentials.
- This is because {{ oidc_provider.name }} does not provide "end_session_endpoint" in its metadata - (see: {{ oidc_provider._server_metadata_url }}). + This is because {{ auth_provider.name }} does not provide "end_session_endpoint" in its metadata + (see: {{ auth_provider.authlib_client._server_metadata_url }}). You can just also go back to the application home page, but - it recommended to go to the OIDC provider's site + it recommended to go to the OIDC provider's site and log out explicitely from there. {% endblock %} From e56be3c378e10e4ac972a3901021226ed26c7c1f Mon Sep 17 00:00:00 2001 From: phil
-
{% endif %}
From 381ce1ebc175d899cca49de14b8b7b6a6b263866 Mon Sep 17 00:00:00 2001
From: phil - Resources validated by scope: - -
-
-
-
Resources validated by role:
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ {% if resource_providers %}
+ + Resource providers (validated by scope): + +
+ {% for name, resource_provider in resource_providers.items() %}
+ {% if resource_provider.default_resource_id %}
+
{% endif %}
From 0464047f8a6d17590f06fc7c52c4b1d4e007b2ce Mon Sep 17 00:00:00 2001
From: phil
- |
{{ provider.name }}
+ {{ provider.hint }}
|
@@ -62,42 +66,17 @@
{% endif %}
- - Resources validated by role: - -
-
-
{% if resource_providers %}
- Resource providers (validated by scope): + Resource providers:
{% for name, resource_provider in resource_providers.items() %}
- {% if resource_provider.default_resource_id %}
-
{% endif %}
From 5bd4b8280427d4a070a724cee141ee52ed3664a4 Mon Sep 17 00:00:00 2001
From: phil
{% if resource_providers %}
- Resource providers: + {{ auth_provider.name }} provides these resources:
{% for name, resource_provider in resource_providers.items() %}
{% if resource_provider.default_resource_id %}
-
From c89ca4098b2165014890af89caebde7310b88db0 Mon Sep 17 00:00:00 2001
From: phil
- {% if resource_providers %}
+
+ {% if resources %}
{{ auth_provider.name }} provides these resources:
- {% for name, resource_provider in resource_providers.items() %}
- {% if resource_provider.default_resource_id %}
-
{% endif %}
+ {% if resource_providers %}
+ {{ auth_provider.name }} can request resources from third party resource providers: + {% for resource_provider in resource_providers %} +
+ {{ resource_provider.name }}
+ {% for resource in resource_provider.resources %}
+
+ {% endfor %}
+ {% endif %}
From e925f2176258d4f73b5b7e565123652da02d6b12 Mon Sep 17 00:00:00 2001
From: phil - {{ auth_provider.name }} provides these resources: + This application provides all these resources, eventually protected with roles:
{% for name, resource in resources.items() %}
@@ -92,7 +92,7 @@
{% endif %}
{% if resource_providers %}
- {{ auth_provider.name }} can request resources from third party resource providers: +{{ auth_provider.name }} allows this applicaiton to request resources from third party resource providers: {% for resource_provider in resource_providers %}
{{ resource_provider.name }}
From ecdd3702f85c04e62741fa15910e25f425d5c20d Mon Sep 17 00:00:00 2001
From: phil - This application provides all these resources, eventually protected with roles: + This application provides all these resources, eventually protected with scope or roles:
{% for name, resource in resources.items() %}
From 347c3953943a1e0b7d84d5555727fae2f1d104be Mon Sep 17 00:00:00 2001
From: phil
-
{% if resources %}
- - This application provides all these resources, eventually protected with scope or roles: - +This application provides all these resources, eventually protected with scope or roles:
{% for name, resource in resources.items() %}
{% if resource.default_resource_id %}
@@ -91,8 +87,29 @@
{% endfor %}
{% endif %}
+ {% if auth_provider.resources %}
+ {{ auth_provider.name }} is also defined as a provider for these resources: +
+ {% for resource in auth_provider.resources %}
+ {% if resource.default_resource_id %}
+
+ {% endif %}
{% if resource_providers %}
- {{ auth_provider.name }} allows this applicaiton to request resources from third party resource providers: +{{ auth_provider.name }} allows this application to request resources from third party resource providers: {% for resource_provider in resource_providers %}
{{ resource_provider.name }}
From 4c2b197850a1e4e16f7b4d690eeb0ecd123422c5 Mon Sep 17 00:00:00 2001
From: phil v. {{ __version__}}
OIDC-test - FastAPI client{% block content %} {% endblock %} From b4653947660be16fbfee161fc4c52e2f4747f0f9 Mon Sep 17 00:00:00 2001 From: phil |