Skip to content

Commit ef81407

Browse files
committed
Fix
1 parent 864ceb2 commit ef81407

2 files changed

Lines changed: 55 additions & 15 deletions

File tree

graphistry/arrow_uploader.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import List, Optional, Dict, Any
22

3-
import io, pyarrow as pa, requests, sys
3+
import base64, io, json, pyarrow as pa, requests, sys
44

55
from graphistry.privacy import Mode, Privacy, ModeAction
66
from graphistry.otel import inject_trace_headers
@@ -21,6 +21,19 @@
2121
from graphistry.models.types import ValidationParam
2222
logger = setup_logger(__name__)
2323

24+
25+
def _personal_org_from_jwt(token: str) -> Optional[str]:
26+
"""Decode JWT payload (no verification) to extract username as personal-org slug."""
27+
try:
28+
parts = token.split('.')
29+
if len(parts) < 2:
30+
return None
31+
segment = parts[1] + '=' * (4 - len(parts[1]) % 4)
32+
return json.loads(base64.urlsafe_b64decode(segment)).get('username')
33+
except Exception:
34+
return None
35+
36+
2437
class ArrowUploader:
2538

2639
def __init__(
@@ -428,15 +441,20 @@ def sso_get_token(self, state):
428441
self.token = token_value
429442

430443
active_org = data.get('active_organization')
431-
if not active_org or not active_org.get('slug'):
432-
raise Exception(
433-
"SSO response missing active organization; see graphistry/graphistry#2933"
434-
)
444+
slug = active_org.get('slug') if isinstance(active_org, dict) else None
445+
446+
if not slug:
447+
# New SSO users may have no active_org yet; fall back to personal org (slug == username)
448+
slug = _personal_org_from_jwt(token_value)
449+
if slug:
450+
logger.info("SSO response missing active_organization; falling back to personal org: %s", slug)
451+
else:
452+
logger.warning("SSO response missing active_organization and JWT has no username; proceeding without org")
435453

436-
slug = active_org['slug']
437-
logger.debug("@ArrowUploader.sso_get_token, org_name: %s", slug)
438-
self.org_name = slug
439-
self._switch_org(slug, token_value or self.token)
454+
if slug:
455+
logger.debug("@ArrowUploader.sso_get_token, org_name: %s", slug)
456+
self.org_name = slug
457+
self._switch_org(slug, token_value)
440458

441459
except Exception as e:
442460
logger.error('Unexpected SSO authentication error: %s', out, exc_info=True)

graphistry/tests/test_arrow_uploader.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22

3-
import graphistry, pandas as pd, pytest, unittest
3+
import base64, graphistry, json, pandas as pd, pytest, unittest
44
try:
55
import mock # type: ignore
66
except ImportError: # pragma: no cover - fallback for stdlib-only envs
@@ -473,19 +473,41 @@ def test_sso_login_get_sso_token_ok(self, mock_get):
473473
mock_switch.assert_called_once_with('mock-org', '123')
474474

475475
@mock.patch('requests.get')
476-
def test_sso_get_token_missing_org_raises(self, mock_get):
476+
def test_sso_get_token_missing_org_falls_back_to_personal(self, mock_get):
477+
payload = base64.urlsafe_b64encode(
478+
json.dumps({'user_id': 1, 'username': 'testuser', 'exp': 9999999999}).encode()
479+
).rstrip(b'=').decode()
480+
fake_token = f"eyJhbGciOiJIUzI1NiJ9.{payload}.fakesig"
477481

478482
mock_resp = self._mock_response(
479483
json_data={
480484
'status': 'OK',
481485
'message': 'State is valid',
482-
'data': {
483-
'token': '123',
484-
}
486+
'data': {'token': fake_token},
485487
})
486488
mock_get.return_value = mock_resp
487489

488490
au = ArrowUploader()
491+
with mock.patch.object(ArrowUploader, "_switch_org") as mock_switch:
492+
au.sso_get_token(state='ignored-valid')
489493

490-
with pytest.raises(Exception):
494+
assert au.token == fake_token
495+
assert au.org_name == 'testuser'
496+
mock_switch.assert_called_once_with('testuser', fake_token)
497+
498+
@mock.patch('requests.get')
499+
def test_sso_get_token_missing_org_no_username_in_jwt(self, mock_get):
500+
mock_resp = self._mock_response(
501+
json_data={
502+
'status': 'OK',
503+
'message': 'State is valid',
504+
'data': {'token': '123'},
505+
})
506+
mock_get.return_value = mock_resp
507+
508+
au = ArrowUploader()
509+
with mock.patch.object(ArrowUploader, "_switch_org") as mock_switch:
491510
au.sso_get_token(state='ignored-valid')
511+
512+
assert au.token == '123'
513+
mock_switch.assert_not_called()

0 commit comments

Comments
 (0)