-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add AuthorizerLoginManager (#1287) Add a commonly requested class. Documented expected use, and with unit tests. [sc-24329] * Add two additional tests [sc-24329] --------- Co-authored-by: Kevin Hunter Kesling <[email protected]>
- Loading branch information
1 parent
5895588
commit 8f5b590
Showing
5 changed files
with
147 additions
and
39 deletions.
There are no files selected for viewing
6 changes: 6 additions & 0 deletions
6
changelog.d/20230902_101610_rchard_add_token_login_manager.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
New Functionality | ||
^^^^^^^^^^^^^^^^^ | ||
|
||
- Added a new ``AuthorizerLoginManager`` to create a login_manager from | ||
existing tokens. This removes the need to implement a custom login manager | ||
to create a client from authorizers. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
compute_sdk/globus_compute_sdk/sdk/login_manager/authorizer_login_manager.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
from __future__ import annotations | ||
|
||
import logging | ||
|
||
import globus_sdk | ||
from globus_compute_sdk.sdk.login_manager.manager import LoginManager | ||
from globus_compute_sdk.sdk.login_manager.protocol import LoginManagerProtocol | ||
from globus_compute_sdk.sdk.web_client import WebClient | ||
from globus_sdk.scopes import AuthScopes | ||
|
||
from .manager import ComputeScopeBuilder | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
ComputeScopes = ComputeScopeBuilder() | ||
|
||
|
||
class AuthorizerLoginManager(LoginManagerProtocol): | ||
""" | ||
Implements a LoginManager that can be instantiated with authorizers. | ||
This manager can be used to create an Executor with authorizers created | ||
from previously acquired tokens, rather than requiring a Native App login | ||
flow or Client credentials. | ||
""" | ||
|
||
def __init__(self, authorizers: dict[str, globus_sdk.RefreshTokenAuthorizer]): | ||
self.authorizers = authorizers | ||
|
||
def get_auth_client(self) -> globus_sdk.AuthClient: | ||
return globus_sdk.AuthClient(authorizer=self.authorizers[AuthScopes.openid]) | ||
|
||
def get_web_client( | ||
self, *, base_url: str | None = None, app_name: str | None = None | ||
) -> WebClient: | ||
return WebClient( | ||
base_url=base_url, | ||
app_name=app_name, | ||
authorizer=self.authorizers[ComputeScopes.resource_server], | ||
) | ||
|
||
def ensure_logged_in(self): | ||
"""Ensure authorizers for each of the required scopes are present.""" | ||
|
||
for server in LoginManager.SCOPES: | ||
if server not in self.authorizers: | ||
log.error(f"Required authorizer for {server} is not present.") | ||
raise LookupError( | ||
f"{type(self).__name__} could not find authorizer for {server}" | ||
) | ||
|
||
def logout(self): | ||
log.warning(f"Logout cannot be invoked from an {type(self).__name__}.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
from itertools import chain, combinations | ||
|
||
import pytest | ||
from globus_compute_sdk.sdk.login_manager import AuthorizerLoginManager, LoginManager | ||
from globus_sdk.scopes import AuthScopes | ||
|
||
CID_KEY = "FUNCX_SDK_CLIENT_ID" | ||
CSC_KEY = "FUNCX_SDK_CLIENT_SECRET" | ||
_MOCK_BASE = "globus_compute_sdk.sdk.login_manager." | ||
|
||
|
||
@pytest.fixture | ||
def logman(mocker, tmp_path): | ||
home = mocker.patch(f"{_MOCK_BASE}tokenstore._home") | ||
home.return_value = tmp_path | ||
return AuthorizerLoginManager({}) | ||
|
||
|
||
def test_auth_client_requires_authorizer_openid_scope(mocker): | ||
alm = AuthorizerLoginManager({}) | ||
with pytest.raises(KeyError) as pyt_exc: | ||
alm.get_auth_client() | ||
assert AuthScopes.openid in str(pyt_exc) | ||
|
||
|
||
def test_does_not_open_local_cred_storage(mocker, randomstring): | ||
test_authorizer = randomstring() | ||
mock_lm = mocker.patch(f"{_MOCK_BASE}authorizer_login_manager.LoginManager") | ||
mock_lm.SCOPES = {test_authorizer} | ||
with pytest.raises(LookupError): | ||
AuthorizerLoginManager({}).ensure_logged_in() | ||
|
||
alm = AuthorizerLoginManager({test_authorizer: "asdf"}) | ||
assert alm.ensure_logged_in() is None, "Test setup: verified SCOPES is checked" | ||
assert not mock_lm.called, "Do not instantiate; do not create creds storage" | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"missing_keys", | ||
list( | ||
chain( | ||
combinations(LoginManager.SCOPES, 1), | ||
combinations(LoginManager.SCOPES, 2), | ||
[()], | ||
) | ||
), | ||
) | ||
def test_ensure_logged_in(mocker, logman, missing_keys): | ||
_authorizers = dict(LoginManager.SCOPES) | ||
for k in missing_keys: | ||
_authorizers.pop(k, None) | ||
|
||
logman.authorizers = _authorizers | ||
|
||
if missing_keys: | ||
with pytest.raises(LookupError) as err: | ||
logman.ensure_logged_in() | ||
|
||
assert f"could not find authorizer for {missing_keys[0]}" in err.value.args[0] | ||
|
||
|
||
def test_warns_upon_logout_attempts(mocker): | ||
mock_log = mocker.patch(f"{_MOCK_BASE}authorizer_login_manager.log") | ||
alm = AuthorizerLoginManager({}) | ||
assert not mock_log.warning.called | ||
alm.logout() | ||
assert mock_log.warning.called |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters