From f8425b6520c9def65bd793b5f02731be571f0488 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Tue, 1 Jul 2025 13:33:52 +0530 Subject: [PATCH] feat(OAuth2): support RFC 8414 This allows an OAuth client to get metadata about the auth server, i.e. the frappe bench being used as an OAuth2 auth server. Metadata includes values for auth server urls and endpoints and supported types and modes. --- frappe/app.py | 5 ++++ frappe/integrations/oauth2.py | 48 ++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/frappe/app.py b/frappe/app.py index 81194d7606..910df78ae2 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -125,6 +125,11 @@ def application(request: Request): elif request.path.startswith("/private/files/"): response = frappe.utils.response.download_private_file(request.path) + elif request.path.startswith("/.well-known/oauth-authorization-server") and request.method == "GET": + from frappe.integrations.oauth2 import get_authorization_server_metadata + + response = get_authorization_server_metadata() + elif request.method in ("GET", "HEAD", "POST"): response = get_response() diff --git a/frappe/integrations/oauth2.py b/frappe/integrations/oauth2.py index 59e9f675b6..187de58bd8 100644 --- a/frappe/integrations/oauth2.py +++ b/frappe/integrations/oauth2.py @@ -1,5 +1,5 @@ import json -from urllib.parse import quote, urlencode +from urllib.parse import quote, urlencode, urlparse from oauthlib.oauth2 import FatalClientError, OAuth2Error from oauthlib.openid.connect.core.endpoints.pre_configured import Server as WebApplicationServer @@ -244,3 +244,49 @@ def introspect_token(token=None, token_type_hint=None): except Exception: frappe.local.response = frappe._dict({"active": False}) + + +def get_authorization_server_metadata(): + """ + Creates response for the /.well-known/oauth-authorization-server endpoint. + + Reference: https://datatracker.ietf.org/doc/html/rfc8414 + """ + from werkzeug import Response + + response = Response() + response.mimetype = "application/json" + response.data = frappe.as_json(_get_authorization_server_metadata()) + return response + + +def _get_authorization_server_metadata(): + """ + Responds with the authorization server metadata. + + Reference: https://datatracker.ietf.org/doc/html/rfc8414#section-2 + + Note: + Value for response_types_supported does not include token because, PKCE + token flow is not supported. Responding with token in the redirect URL + is an unsafe practice, so code is the only supported response type. + """ + + request_url = urlparse(frappe.request.url) + issuer = f"{request_url.scheme}://{request_url.netloc}" + return dict( + issuer=issuer, + authorization_endpoint=f"{issuer}/api/method/frappe.integrations.oauth2.authorize", + token_endpoint=f"{issuer}/api/method/frappe.integrations.oauth2.get_token", + response_types_supported=["code"], + response_modes_supported=["query"], + grant_types_supported=["authorization_code", "refresh_token"], + token_endpoint_auth_methods_supported=["client_secret_basic"], + service_documentation="https://docs.frappe.io/framework/user/en/guides/integration/how_to_set_up_oauth#add-a-client-app", + revocation_endpoint=f"{issuer}/api/method/frappe.integrations.oauth2.revoke_token", + revocation_endpoint_auth_methods_supported=["client_secret_basic"], + introspection_endpoint=f"{issuer}/api/method/frappe.integrations.oauth2.introspect_token", + userinfo_endpoint=f"{issuer}/api/method/frappe.integrations.oauth2.openid_profile", + # registration_endpoint=f"{issuer}/api/method/frappe.integrations.oauth2.register_client", # TODO: RFC 7591 + # scopes_supported=[], + )