From 0a5f0ab0e3848185e27a76d407d08df1ba6e5e1d Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 22 Oct 2024 16:31:42 -0400 Subject: [PATCH 1/5] get token from callback_context --- dash_enterprise_auth/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dash_enterprise_auth/__init__.py b/dash_enterprise_auth/__init__.py index a72ebf0..884a12c 100644 --- a/dash_enterprise_auth/__init__.py +++ b/dash_enterprise_auth/__init__.py @@ -97,7 +97,10 @@ def create_logout_button(label="Logout", style=None): def _get_decoded_token(name): - token = _flask.request.cookies.get(name) + if hasattr(_dash.callback_context, "cookies"): + token = _dash.callback_context.cookies.get(name) + else: + token = _flask.request.cookies.get(name) return _b64.b64decode(token) From d81f77c409e185e159f55c0000a988bd524d9ffb Mon Sep 17 00:00:00 2001 From: philippe Date: Wed, 23 Oct 2024 09:39:57 -0400 Subject: [PATCH 2/5] safer handling of token --- dash_enterprise_auth/__init__.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/dash_enterprise_auth/__init__.py b/dash_enterprise_auth/__init__.py index 884a12c..25654b7 100644 --- a/dash_enterprise_auth/__init__.py +++ b/dash_enterprise_auth/__init__.py @@ -96,19 +96,32 @@ def create_logout_button(label="Logout", style=None): ) +def _raise_context_error(): + raise RuntimeError( + "Could not find user token from the context.\n" + "Make sure you are running inside a flask request or a dash callback." + ) + + def _get_decoded_token(name): - if hasattr(_dash.callback_context, "cookies"): - token = _dash.callback_context.cookies.get(name) - else: + token = None + if _flask.has_request_context(): token = _flask.request.cookies.get(name) + if not token and hasattr(_dash.callback_context, "cookies"): + # + token = _dash.callback_context.cookies.get(name) + if token is None: + _raise_context_error() return _b64.b64decode(token) -@_need_request_context def get_user_data(): jwks_url = _os.getenv("DASH_JWKS_URL") info_url = _os.getenv("DASH_USER_INFO_URL") if not jwks_url: + if not _flask.has_request_context(): + # Old DE4 should always be in a request context. + _raise_context_error() return _json.loads(_flask.request.headers.get("Plotly-User-Data", "{}")) try: jwks_client = UaPyJWKClient(jwks_url) @@ -145,7 +158,6 @@ def get_user_data(): return {} -@_need_request_context def get_username(): """ Get the current user. From 0f23f4e101bcb47418a8d7652af1d8e95c65c5b1 Mon Sep 17 00:00:00 2001 From: Hammad Date: Mon, 28 Oct 2024 20:41:58 -0400 Subject: [PATCH 3/5] Change error raised when called in notebook env --- dash_enterprise_auth/__init__.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/dash_enterprise_auth/__init__.py b/dash_enterprise_auth/__init__.py index 25654b7..9d7447a 100644 --- a/dash_enterprise_auth/__init__.py +++ b/dash_enterprise_auth/__init__.py @@ -3,6 +3,7 @@ Methods to integrate dash apps with the authentication from Dash Enterprise. """ + import datetime as _dt import os as _os import platform as _platform @@ -19,6 +20,7 @@ import dash as _dash + if hasattr(_dash, "dcc"): _dcc = _dash.dcc else: @@ -53,6 +55,7 @@ def _wrap(*args, **kwargs): f" context to run. Make sure to run `{func.__name__}` from a callback." ) return func(*args, **kwargs) + return _wrap @@ -69,9 +72,7 @@ def create_logout_button(label="Logout", style=None): """ logout_url = _os.getenv("DASH_LOGOUT_URL") if not logout_url: - raise RuntimeError( - "DASH_LOGOUT_URL was not set in the environment." - ) + raise RuntimeError("DASH_LOGOUT_URL was not set in the environment.") if not _os.getenv("DASH_JWKS_URL"): return _dcc.LogoutButton( @@ -89,18 +90,28 @@ def create_logout_button(label="Logout", style=None): label, href=logout_url, className="dash-logout-btn", - style={"textDecoration": "none"} + style={"textDecoration": "none"}, ), className="dash-logout-frame", - style=btn_style + style=btn_style, ) def _raise_context_error(): - raise RuntimeError( - "Could not find user token from the context.\n" - "Make sure you are running inside a flask request or a dash callback." + try: + is_jupyter_kernel = ( + get_ipython().__class__.__name__ == "ZMQInteractiveShell" # type: ignore ) + except NameError: + is_jupyter_kernel = False + + raise RuntimeError( + "dash-enterprise-auth functions should be called in a flask request context or dash callback and will not run in a notebook cell.\n" + "This codeblock will still run correctly in your App Studio preview or deployed Dash app." + if is_jupyter_kernel + else "Could not find user token from the context.\n" + "Make sure you are running inside a flask request or a dash callback." + ) def _get_decoded_token(name): @@ -108,7 +119,7 @@ def _get_decoded_token(name): if _flask.has_request_context(): token = _flask.request.cookies.get(name) if not token and hasattr(_dash.callback_context, "cookies"): - # + # token = _dash.callback_context.cookies.get(name) if token is None: _raise_context_error() From 1ade6c8d770adcadb234b9130453a8fd12661531 Mon Sep 17 00:00:00 2001 From: philippe Date: Tue, 29 Oct 2024 15:04:00 -0400 Subject: [PATCH 4/5] Only raise error if the contexts are not present, return if no token --- dash_enterprise_auth/__init__.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/dash_enterprise_auth/__init__.py b/dash_enterprise_auth/__init__.py index 9d7447a..cdc44aa 100644 --- a/dash_enterprise_auth/__init__.py +++ b/dash_enterprise_auth/__init__.py @@ -37,6 +37,7 @@ f" Platform={_platform.system()}/{_platform.release()})" ) +_undefined = object() class UaPyJWKClient(_jwt.PyJWKClient): def fetch_data(self) -> Any: @@ -115,14 +116,15 @@ def _raise_context_error(): def _get_decoded_token(name): - token = None + token = _undefined if _flask.has_request_context(): token = _flask.request.cookies.get(name) if not token and hasattr(_dash.callback_context, "cookies"): - # token = _dash.callback_context.cookies.get(name) - if token is None: + if token is _undefined: _raise_context_error() + if token is None: + return token return _b64.b64decode(token) @@ -138,6 +140,10 @@ def get_user_data(): jwks_client = UaPyJWKClient(jwks_url) token = _get_decoded_token("kcIdToken") + + if not token: + return {} + signing_key = jwks_client.get_signing_key_from_jwt(token) info = _jwt.decode( From 493e626f67299843c2e521abc4c0e3cfd0943810 Mon Sep 17 00:00:00 2001 From: philippe Date: Mon, 11 Nov 2024 11:49:53 -0500 Subject: [PATCH 5/5] fix undefined check --- dash_enterprise_auth/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dash_enterprise_auth/__init__.py b/dash_enterprise_auth/__init__.py index cdc44aa..fdae00b 100644 --- a/dash_enterprise_auth/__init__.py +++ b/dash_enterprise_auth/__init__.py @@ -119,7 +119,7 @@ def _get_decoded_token(name): token = _undefined if _flask.has_request_context(): token = _flask.request.cookies.get(name) - if not token and hasattr(_dash.callback_context, "cookies"): + if token is _undefined and hasattr(_dash.callback_context, "cookies"): token = _dash.callback_context.cookies.get(name) if token is _undefined: _raise_context_error()