Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev/sso login ux #608

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
89f3d5b
fix (typecheck): fix typecheck issue
vaimdev Oct 17, 2024
cdeaf63
Merge branch 'master' into dev/sso_login_ux
vaimdev Oct 28, 2024
dbad147
feat: Add databricks helper class and functions
vaimdev Oct 28, 2024
799e93b
fix (refresh token, lint): Fix refresh token and lint issue
vaimdev Oct 28, 2024
e1737d8
fix (lint): fix lint issue
vaimdev Oct 29, 2024
caa7809
fix (databricks helper): fix missing function definition
vaimdev Oct 29, 2024
71c5eb1
fix (databricks helpers): fix function call namespace
vaimdev Oct 30, 2024
c187ead
wip (sso logic): Add SSO flow token verification in plot function, wh…
vaimdev Nov 5, 2024
412e944
wip (sso): fix whether to throw exception when fail to get token
vaimdev Nov 6, 2024
9283e8a
wip (sso): fix message display logic
vaimdev Nov 6, 2024
be26dee
fix (sso): fix flow
vaimdev Nov 6, 2024
5ea4841
fix (sso): fix display message flow
vaimdev Nov 6, 2024
8a482b5
fix (sso): Graph is not shown in dashboard due to only the first HTML…
vaimdev Nov 7, 2024
ab2794c
wip (sso): fix syntax error
vaimdev Nov 7, 2024
5b35284
wip (sso): debugging code
vaimdev Nov 8, 2024
8355b0f
wip (debug): add debug print
vaimdev Nov 8, 2024
1a2ab33
wip (fix): syntax error
vaimdev Nov 8, 2024
a536648
wip (debug):debugging print
vaimdev Nov 8, 2024
dd29430
wip (debug): debugging print
vaimdev Nov 8, 2024
36fcfea
wip (sso): attempt to display error in iframe
vaimdev Nov 8, 2024
4820620
wip (databricks): return error msg html in the case of plot with inva…
vaimdev Nov 8, 2024
2d6ad0b
wip (sso): display html message before throw exception
vaimdev Nov 8, 2024
05d0dff
wip(sso): fix error
vaimdev Nov 8, 2024
fd46826
wip (sso): fix syntax error
vaimdev Nov 9, 2024
4816fd6
wip (sso): clean up code
vaimdev Nov 9, 2024
bff2459
Update docstr for new/updated methods
sabizmil Nov 11, 2024
4b0b129
fix (lint): fix lint issue
vaimdev Nov 13, 2024
c048ee2
fix (doc): Add documentations
vaimdev Nov 25, 2024
6a46ca2
Merge branch 'master' into dev/sso_login_ux
vaimdev Dec 3, 2024
274126d
doc (databricks): Add documentation the docString, add changelog
vaimdev Dec 3, 2024
6ac7cb6
fix (message): Adjust the message
vaimdev Dec 4, 2024
a5779b4
fix (message): adjust message
vaimdev Dec 4, 2024
4df33e2
fix (util): revert the make_iframe function
vaimdev Dec 25, 2024
e3b1e8c
fix (util): fix make_iframe
vaimdev Dec 25, 2024
b53051a
fix (util): Fix syntax error
vaimdev Dec 25, 2024
5a14983
Merge branch 'master' into dev/sso_login_ux
vaimdev Dec 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion graphistry/PlotterBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -1403,7 +1403,20 @@ def plot(
info = PyGraphistry._etl1(dataset)
elif api_version == 3:
logger.debug("3. @PloatterBase plot: PyGraphistry.org_name(): {}".format(PyGraphistry.org_name()))
PyGraphistry.refresh()

if not PyGraphistry.api_token() and PyGraphistry.sso_state(): # if it is sso login
if in_ipython() or in_databricks() or PyGraphistry._config["sso_opt_into_type"] == 'display':
PyGraphistry.sso_wait_for_token_text_display()
if not PyGraphistry.sso_verify_token_display():
from IPython.core.display import HTML
msg_html = "<strong>Invalid token due to login timeout</strong>"
return HTML(msg_html)
else:
PyGraphistry.sso_repeat_get_token()


else: # if not sso mode, just refresh to make sure token is valid
PyGraphistry.refresh()
logger.debug("4. @PloatterBase plot: PyGraphistry.org_name(): {}".format(PyGraphistry.org_name()))

dataset = self._plot_dispatch(g, n, name, description, 'arrow', self._style, memoize)
Expand Down
4 changes: 3 additions & 1 deletion graphistry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
ArrowFileUploader,
PyGraphistry,
from_igraph,
from_cugraph
from_cugraph,
sso_wait_for_token_display,
register_databricks_sso,
)

from graphistry.compute import (
Expand Down
159 changes: 156 additions & 3 deletions graphistry/pygraphistry.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from . import util
from . import bolt_util
from .plotter import Plotter
from .util import in_databricks, setup_logger, in_ipython, make_iframe
from .util import in_databricks, setup_logger, in_ipython, make_iframe, display_message_html
from .exceptions import SsoRetrieveTokenTimeoutException, TokenExpireException

from .messages import (
Expand Down Expand Up @@ -135,6 +135,12 @@ def __reset_token_creds_in_memory():
PyGraphistry._config["api_key"] = None
PyGraphistry._is_authenticated = False

@staticmethod
def __reset_sso_variables_in_memory():
"""Reset the sso related variable in memory, used when switching hosts, switching register method"""

PyGraphistry._config["sso_state"] = None
PyGraphistry._config["sso_opt_into_type"] = None


@staticmethod
Expand Down Expand Up @@ -271,7 +277,7 @@ def _handle_auth_url(auth_url, sso_timeout, sso_opt_into_type):
if in_ipython() or in_databricks() or sso_opt_into_type == 'display': # If run in notebook, just display the HTML
# from IPython.core.display import HTML
from IPython.display import display, HTML
display(HTML(f'<a href="{auth_url}" target="_blank">Login SSO</a>'))
display(HTML(f'<a href="{auth_url}" target="_blank">Login SSO</a><br /><span>Please click the above URL to open browser to login</span>'))
vaimdev marked this conversation as resolved.
Show resolved Hide resolved
print("Please click the above URL to open browser to login")
print(f"If you cannot see the URL, please open browser, browse to this URL: {auth_url}")
print("Please close browser tab after SSO login to back to notebook")
Expand Down Expand Up @@ -406,6 +412,8 @@ def refresh(token=None, fail_silent=False):
logger.debug("2. @PyGraphistry refresh :relogin")
if isinstance(e, TokenExpireException):
print("Token is expired, you need to relogin")
PyGraphistry._config['api_token'] = None
PyGraphistry._is_authenticated = False
return PyGraphistry.relogin()

if not fail_silent:
Expand All @@ -414,7 +422,7 @@ def refresh(token=None, fail_silent=False):

@staticmethod
def verify_token(token=None, fail_silent=False) -> bool:
"""Return True iff current or provided token is still valid"""
"""Return True if current or provided token is still valid"""
using_self_token = token is None
try:
logger.debug("JWT refresh")
Expand Down Expand Up @@ -570,6 +578,11 @@ def certificate_validation(value=None):
def set_bolt_driver(driver=None):
PyGraphistry._config["bolt_driver"] = bolt_util.to_bolt_driver(driver)

@staticmethod
def set_sso_opt_into_type(value: Optional[str]):
PyGraphistry._config["sso_opt_into_type"] = value


@staticmethod
def register(
key: Optional[str] = None,
Expand Down Expand Up @@ -707,6 +720,8 @@ def register(
PyGraphistry.set_bolt_driver(bolt)
# Reset token creds
PyGraphistry.__reset_token_creds_in_memory()
# Reset sso related variables in memory
PyGraphistry.__reset_sso_variables_in_memory()

if not (username is None) and not (password is None):
PyGraphistry.login(username, password, org_name)
Expand Down Expand Up @@ -2446,7 +2461,140 @@ def _handle_api_response(response):
logger.error('Error: %s', response, exc_info=True)
raise Exception("Unknown Error")

@staticmethod
def sso_repeat_get_token(repeat: int = 20, wait: int = 5):
""" Function to repeatly call to obtain the jwt token after sso login
Function to obtain the JWT token after SSO login
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sabizmil can you check all public method docstr as these show up in the readthedocs?

Spelling, grammar, docstr types, and examples, following format of the rest

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in the latest commit

"""

for _ in range(repeat):
token = PyGraphistry.sso_get_token()
if token:
return token
time.sleep(wait)

return

@staticmethod
def sso_wait_for_token_display(repeat: int = 20, wait: int = 5, fail_silent: bool = False, display_mode: str = 'text'):
if display_mode == 'html':
PyGraphistry.sso_wait_for_token_html_display(repeat, wait, fail_silent)
else:
PyGraphistry.sso_wait_for_token_text_display(repeat, wait, fail_silent)

@staticmethod
def sso_wait_for_token_text_display(repeat: int = 20, wait: int = 5, fail_silent: bool = False):
"""Get the JWT token for SSO login and display corresponding message in text
Get the JWT token for SSO login and display corresponding message in text
"""
if not PyGraphistry.api_token():
msg_text = '....'
if not PyGraphistry.sso_repeat_get_token(repeat, wait):
msg_text = f'{msg_text}\nFailed to get token after {repeat*wait} seconds ....'
vaimdev marked this conversation as resolved.
Show resolved Hide resolved
if not fail_silent:
msg = f"Failed to get token after {repeat*wait} seconds. Please re-run the login process"
vaimdev marked this conversation as resolved.
Show resolved Hide resolved
if in_ipython() or in_databricks() or PyGraphistry.set_sso_opt_into_type == "display":
display_message_html(f"<strong>{msg}</strong>")
raise Exception(msg)
else:
msg_text = f'{msg_text}\nGot token'
vaimdev marked this conversation as resolved.
Show resolved Hide resolved
print(msg_text)
return

msg_text = f'{msg_text}\nGot token'
vaimdev marked this conversation as resolved.
Show resolved Hide resolved
print(msg_text)
else:
print('Token is valid, no waiting required.')


@staticmethod
def sso_wait_for_token_html_display(repeat: int = 20, wait: int = 5, fail_silent: bool = False):
"""Get the JWT token for SSO login and display corresponding message in HTML
Get the JWT token for SSO login and display corresponding message in HTML
"""
from IPython.display import display, HTML
if not PyGraphistry.api_token():
msg_html = '<br /><strong> .... </strong>'
if not PyGraphistry.sso_repeat_get_token(repeat, wait):
msg_html = f'{msg_html}<br /><strong>Failed to get token after {repeat*wait} seconds .... </strong>'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

msg_html = f'{msg_html}<br /><strong>Unable to retrieve token after {repeat*wait} seconds.</strong> Please try logging in again.'

if not fail_silent:
raise Exception(f"Failed to get token after {repeat*wait} seconds. Please re-run the login process")
vaimdev marked this conversation as resolved.
Show resolved Hide resolved
else:
msg_html = f'{msg_html}<br /><strong>Got token</strong>'
display(HTML(msg_html))
return

msg_html = f'{msg_html}<br /><strong>Got token</strong>'
display(HTML(msg_html))
else:
display(HTML('<br /><strong>Token is valid, no waiting required.</strong>'))
vaimdev marked this conversation as resolved.
Show resolved Hide resolved


@staticmethod
def sso_verify_token_display(
repeat: int = 20,
wait: int = 5,
display_mode: str = 'text'
) -> bool:
if display_mode == 'html':
from IPython.display import display, HTML, clear_output
clear_output()

required_login = False
token = PyGraphistry.api_token()
if token:
is_valid = PyGraphistry.verify_token()
print(f"is_valid : {is_valid}")
if not is_valid:
print("***********token not valid, refresh token*****************")
if display_mode == 'html':
display(HTML('<br /><strong>Refresh token ....</strong>'))
vaimdev marked this conversation as resolved.
Show resolved Hide resolved
try:
PyGraphistry.refresh()
except Exception:
required_login = True

else:
print("Token is still valid")
if display_mode == 'html':
display(HTML('<br /><strong>Token is still valid ....</strong>'))
vaimdev marked this conversation as resolved.
Show resolved Hide resolved

else:
required_login = True

if required_login:
print("***********Prepare to sign in*****************")
msg_html = f'<br /><strong>Prepare to sign in ....</strong><br><strong>Please Login with the link appear later. Waiting for success login for {repeat*wait} seconds, please login within {wait} seconds....</strong><br /><strong>Please close the browser tab and come back to dashboard....</strong>'
vaimdev marked this conversation as resolved.
Show resolved Hide resolved
display(HTML(msg_html))


return not required_login


# Databricks Dashboard SSO helper functions
class DatabricksHelper():
"""Helper class for databricks.

**Helper class to improve the sso login flow**

"""
@staticmethod
def register_databricks_sso(
server: Optional[str] = None,
org_name: Optional[str] = None,
idp_name: Optional[str] = None,
**kwargs
):
if not PyGraphistry.api_token():
PyGraphistry.register(api=3, protocol="https", server=server, is_sso_login=True, org_name=org_name, idp_name=idp_name, sso_timeout=None, sso_opt_into_type="display")


# @staticmethod
# def databricks_sso_login(server="hub.graphistry.com", org_name=None, idp_name=None, retry=5, wait=20):
# from IPython.display import clear_output
# clear_output()
# if not PyGraphistry.api_token():
# PyGraphistry.register(api=3, protocol="https", server=server, is_sso_login=True, org_name=org_name, idp_name=idp_name, sso_timeout=None, sso_opt_into_type="display")


client_protocol_hostname = PyGraphistry.client_protocol_hostname
Expand Down Expand Up @@ -2501,6 +2649,11 @@ def _handle_api_response(response):
personal_key_secret = PyGraphistry.personal_key_secret
switch_org = PyGraphistry.switch_org

# databricks dashboard helper functions
sso_wait_for_token_display = PyGraphistry.sso_wait_for_token_display
sso_verify_token_display = PyGraphistry.sso_verify_token_display
sso_repeat_get_token = PyGraphistry.sso_repeat_get_token
register_databricks_sso = PyGraphistry.DatabricksHelper.register_databricks_sso


class NumpyJSONEncoder(json.JSONEncoder):
Expand Down
14 changes: 11 additions & 3 deletions graphistry/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import uuid
import warnings
from functools import lru_cache
from typing import Any
from typing import Any, Optional
from collections import UserDict

from .constants import VERBOSE, CACHE_COERCION_SIZE, TRACE
Expand Down Expand Up @@ -172,8 +172,16 @@ def check_set_memoize(
weakref[hashed] = w
return False

def display_message_html(message: str, cleared: Optional[bool] = False):
from IPython.display import display, HTML, clear_output

def make_iframe(url, height, extra_html="", override_html_style=None):
if cleared:
clear_output()

display(HTML(message))


def make_iframe(url, height, extra_html="", override_html_style=None, srcdoc: Optional[str] = None):
id = uuid.uuid4()

height_str = (
Expand Down Expand Up @@ -203,7 +211,7 @@ def make_iframe(url, height, extra_html="", override_html_style=None):
)

iframe = """
<iframe id="%s" src="%s"
<iframe id="%s" src="%s" {srcdoc}
allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"
oallowfullscreen="true" msallowfullscreen="true"
style="%s"
Expand Down
Loading