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

Fix intermittent globus auth failure #533

Merged
merged 7 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions changelog/533.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix issue issue causing Globus to intermittently fail after auth.
13 changes: 7 additions & 6 deletions dkist/net/globus/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,14 @@ def save_auth_cache(auth_cache):
cache_file.chmod(stat.S_IRUSR | stat.S_IWUSR)


def do_native_app_authentication(client_id, requested_scopes=None): # pragma: no cover
def do_native_app_authentication(client, requested_scopes=None): # pragma: no cover
"""
Does a Native App authentication flow and returns a
dict of tokens keyed by service name.
"""
server = start_local_server()
redirect_uri = f"http://{server.server_address[0]}:{server.server_address[1]}"

client = globus_sdk.NativeAppAuthClient(client_id=client_id)
client.oauth2_start_flow(requested_scopes=SCOPES,
redirect_uri=redirect_uri,
refresh_tokens=True)
Expand Down Expand Up @@ -176,11 +175,11 @@ def get_refresh_token_authorizer(force_reauth=False):
tokens = None
if not force_reauth:
tokens = get_cache_contents()
if not tokens:
tokens = do_native_app_authentication(CLIENT_ID, SCOPES)
save_auth_cache(tokens)

auth_client = globus_sdk.NativeAppAuthClient(client_id=CLIENT_ID)
if not tokens:
tokens = do_native_app_authentication(auth_client, SCOPES)
save_auth_cache(tokens)

transfer_tokens = tokens["transfer.api.globus.org"]

Expand Down Expand Up @@ -212,7 +211,9 @@ def do_reauth(*args, **kwargs):
response_text = getattr(e, "text", "") or getattr(e, "raw_text", "")
if e.http_status == 400 and "invalid_grant" in response_text:
print("Globus login has expired.")
get_refresh_token_authorizer(force_reauth=True)
auths = get_refresh_token_authorizer(force_reauth=True)
if "tfr_client" in kwargs:
kwargs["tfr_client"].authorizer = auths["transfer.api.globus.org"]
return func(*args, **kwargs)

return do_reauth
8 changes: 4 additions & 4 deletions dkist/net/globus/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def get_data_center_endpoint_id():


@ensure_globus_authorized
def get_endpoint_id(endpoint, tfr_client):
def get_endpoint_id(endpoint, *, tfr_client):
"""
Resolve an endpoint description to an ID.

Expand Down Expand Up @@ -120,7 +120,7 @@ def get_endpoint_id(endpoint, tfr_client):


@ensure_globus_authorized
def auto_activate_endpoint(endpoint_id, tfr_client): # pragma: no cover
def auto_activate_endpoint(endpoint_id, *, tfr_client): # pragma: no cover
"""
Perform activation of a Globus endpoint.

Expand Down Expand Up @@ -176,8 +176,8 @@ def get_directory_listing(path, endpoint=None):
tc = get_transfer_client()

if endpoint_id is None:
endpoint_id = get_endpoint_id(endpoint, tc)
auto_activate_endpoint(endpoint_id, tc)
endpoint_id = get_endpoint_id(endpoint, tfr_client=tc)
auto_activate_endpoint(endpoint_id, tfr_client=tc)

response = tc.operation_ls(endpoint_id, path=path.as_posix())
names = [r["name"] for r in response]
Expand Down
8 changes: 6 additions & 2 deletions dkist/net/globus/tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,18 @@ def test_ensure_auth_decorator(mocker):
mock_response.headers = {"Content-Type": "application/json"}
error = globus_sdk.AuthAPIError(mock_response)
reauth = mocker.patch("dkist.net.globus.auth.get_refresh_token_authorizer")
reauth.return_value = {"transfer.api.globus.org": "some new token"}

called = [False]
@ensure_globus_authorized
def test_func():
def test_func(tfr_client):
if not called[0]:
called[0] = True
raise error
return True

assert test_func()
mock_client = mocker.MagicMock()
mock_client.authorizer = "some old token"
assert test_func(tfr_client=mock_client)
assert mock_client.authorizer == "some new token"
reauth.assert_called_once_with(force_reauth=True)
14 changes: 7 additions & 7 deletions dkist/net/globus/tests/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,22 @@ def test_get_endpoint_id_search(mocker, mock_search, endpoint_search, transfer_c
transfer_client = get_transfer_client()

# Test exact display name match
endpoint_id = get_endpoint_id("NCAR Data Sharing Service", transfer_client)
endpoint_id = get_endpoint_id("NCAR Data Sharing Service", tfr_client=transfer_client)
assert endpoint_id == "dd1ee92a-6d04-11e5-ba46-22000b92c6ec"

# Test multiple match fail
with pytest.raises(ValueError, match="Multiple"):
get_endpoint_id(" ", transfer_client)
get_endpoint_id(" ", tfr_client=transfer_client)

# Test just one result
mock_search.return_value = {"DATA": endpoint_search["DATA"][1:2]}
endpoint_id = get_endpoint_id(" ", transfer_client)
endpoint_id = get_endpoint_id(" ", tfr_client=transfer_client)
assert endpoint_id == "dd1ee92a-6d04-11e5-ba46-22000b92c6ec"

# Test no results
mock_search.return_value = {"DATA": []}
with pytest.raises(ValueError, match="No matches"):
get_endpoint_id(" ", transfer_client)
get_endpoint_id(" ", tfr_client=transfer_client)


def test_get_endpoint_id_uuid(mocker, transfer_client, endpoint_search):
Expand All @@ -93,7 +93,7 @@ def test_get_endpoint_id_uuid(mocker, transfer_client, endpoint_search):
new_callable=mocker.PropertyMock)
get_ep_mock.return_value = {"DATA": endpoint_search["DATA"][1:2]}

endpoint_id = get_endpoint_id("dd1ee92a-6d04-11e5-ba46-22000b92c6ec", transfer_client)
endpoint_id = get_endpoint_id("dd1ee92a-6d04-11e5-ba46-22000b92c6ec", tfr_client=transfer_client)
assert endpoint_id == "dd1ee92a-6d04-11e5-ba46-22000b92c6ec"


Expand All @@ -105,11 +105,11 @@ def test_get_endpoint_id_invalid_uuid(mocker, mock_search, transfer_client, endp

# Test Other transfer error
with pytest.raises(globus_sdk.TransferAPIError):
get_endpoint_id("wibble", transfer_client)
get_endpoint_id("wibble", tfr_client=transfer_client)

# Test EndpointNotFound error
mocker.patch.object(err, "code", "EndpointNotFound")
endpoint_id = get_endpoint_id("wibble", transfer_client)
endpoint_id = get_endpoint_id("wibble", tfr_client=transfer_client)
assert endpoint_id == "dd1ee92a-6d04-11e5-ba46-22000b92c6ec"


Expand Down
2 changes: 1 addition & 1 deletion dkist/net/globus/tests/test_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

@pytest.fixture
def mock_endpoints(mocker):
def id_mock(endpoint, tc):
def id_mock(endpoint, tfr_client):
return endpoint
mocker.patch("dkist.net.globus.transfer.auto_activate_endpoint")
return mocker.patch("dkist.net.globus.transfer.get_endpoint_id",
Expand Down
8 changes: 4 additions & 4 deletions dkist/net/globus/transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ def start_transfer_from_file_list(
tc = get_transfer_client()

# Resolve to IDs and activate endpoints
src_endpoint = get_endpoint_id(src_endpoint, tc)
auto_activate_endpoint(src_endpoint, tc)
src_endpoint = get_endpoint_id(src_endpoint, tfr_client=tc)
auto_activate_endpoint(src_endpoint, tfr_client=tc)

dst_endpoint = get_endpoint_id(dst_endpoint, tc)
auto_activate_endpoint(dst_endpoint, tc)
dst_endpoint = get_endpoint_id(dst_endpoint, tfr_client=tc)
auto_activate_endpoint(dst_endpoint, tfr_client=tc)

now = datetime.datetime.now().strftime("%Y-%m-%dT%H-%M")
label = f"DKIST Python Tools - {now}" if label is None else label
Expand Down