-
Notifications
You must be signed in to change notification settings - Fork 23
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
Added globus auth scope show command #1077
Open
derek-globus
wants to merge
14
commits into
globus:main
Choose a base branch
from
derek-globus:scope-show
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
70dcca1
Added globus auth scope show command
derek-globus 9c8e230
Remove list subscripting for py38
derek-globus 914bfbd
Added module for tracking global indentation
derek-globus 28ef3c5
Make it work for py38 again; someday I'll learn
derek-globus 5063012
commit commit commit
derek-globus 5bc3d0f
Try fixing windows
derek-globus eeb9088
Address Windows-specific output differences
kurtmckee 61c1ab1
Max recursion depth in json value typeguard
derek-globus ecf9df7
Added TerminalTextWrapper
derek-globus 9e8e3dc
Comment py38 compatibility decision
derek-globus 799ea26
ArrayMultilineFormatter._indent_element -> _left_pad_element
derek-globus 699bb8d
Made left padding example more normal
derek-globus b56ef11
Make array render control flow more clear
derek-globus 5e38ed8
Render an empty array as []
derek-globus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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 @@ | ||
|
||
### Enhancements | ||
|
||
* Added a new command to display auth scope information: `globus auth scope show`. | ||
|
||
*The "globus auth" command tree is hidden until more commands are added.* |
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
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,13 @@ | ||
from globus_cli.parsing import group | ||
|
||
|
||
@group( | ||
"auth", | ||
lazy_subcommands={ | ||
"scope": (".scope", "scope_command"), | ||
}, | ||
# TODO - Make the auth command public once we have >= 5 subcommands | ||
hidden=True, | ||
) | ||
def auth_command() -> None: | ||
"""Interact with the Globus Auth service.""" |
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,11 @@ | ||
from globus_cli.parsing import group | ||
|
||
|
||
@group( | ||
"scope", | ||
lazy_subcommands={ | ||
"show": (".show", "show_command"), | ||
}, | ||
) | ||
def scope_command() -> None: | ||
"""Interact with a scope in the Globus Auth service.""" |
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,83 @@ | ||
from __future__ import annotations | ||
|
||
import globus_sdk | ||
|
||
from globus_cli.termio import Field, formatters | ||
|
||
SCOPE_SUMMARY_FIELDS = [ | ||
Field("Scope ID", "scope"), | ||
Field("Optional", "optional", formatter=formatters.Bool), | ||
Field( | ||
"Requires Refresh Token", "requires_refresh_token", formatter=formatters.Bool | ||
), | ||
] | ||
|
||
DECORATED_SCOPE_SUMMARY_FIELDS = [ | ||
# Scope summaries don't actually contain a "scope_string" field | ||
# But it's very useful to understanding a dependent scope, so we decorate it in. | ||
Field("Scope String", "scope_string"), | ||
] + SCOPE_SUMMARY_FIELDS | ||
|
||
_BASE_SCOPE_FIELDS = [ | ||
Field("Scope String", "scope_string"), | ||
Field("Scope ID", "id"), | ||
Field("Name", "name"), | ||
Field("Description", "description", wrap_enabled=True), | ||
Field("Client ID", "client"), | ||
Field("Allows Refresh Tokens", "allows_refresh_token", formatter=formatters.Bool), | ||
Field("Required Domains", "required_domains", formatter=formatters.Array), | ||
Field("Advertised", "advertised", formatter=formatters.Bool), | ||
] | ||
|
||
DECORATED_SCOPE_FIELDS = _BASE_SCOPE_FIELDS + [ | ||
Field( | ||
"Dependent Scopes", | ||
"dependent_scopes", | ||
formatter=formatters.ArrayMultilineFormatter( | ||
formatters.RecordFormatter(DECORATED_SCOPE_SUMMARY_FIELDS) | ||
), | ||
), | ||
] | ||
|
||
|
||
class BatchScopeStringResolver: | ||
""" | ||
A resolver for accessing multiple scope strings without making multiple requests. | ||
|
||
The list of scopes ids, provided at initialization, are queried in a batch request | ||
and cached for future access once the first scope string is resolved. | ||
|
||
:param auth_client: The AuthClient to use for scope string resolution. | ||
:param scope_ids: A list of scope IDs to resolve. | ||
:param default: A default string to return in case a scope id couldn't be found. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
auth_client: globus_sdk.AuthClient, | ||
scope_ids: list[str], | ||
default: str | None = "UNKNOWN", | ||
) -> None: | ||
self._auth_client = auth_client | ||
self._scope_ids = scope_ids | ||
self._scope_strings: dict[str, str] | None = None | ||
self._default = default | ||
|
||
def resolve(self, scope_id: str) -> str: | ||
if scope_id not in self._scope_ids: | ||
raise ValueError(f"Scope ID {scope_id} was not registered for resolution.") | ||
elif scope_id not in self.scope_strings: | ||
if self._default is not None: | ||
return self._default | ||
raise ValueError(f"Scope string for {scope_id} could not be retrieved.") | ||
return self.scope_strings[scope_id] | ||
|
||
@property | ||
def scope_strings(self) -> dict[str, str]: | ||
"""A mapping of scope ID to their scope strings.""" | ||
if self._scope_strings is None: | ||
resp = self._auth_client.get_scopes(ids=self._scope_ids) | ||
self._scope_strings = { | ||
scope["id"]: scope["scope_string"] for scope in resp.get("scopes", []) | ||
} | ||
return self._scope_strings |
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,78 @@ | ||
from __future__ import annotations | ||
|
||
import functools | ||
import typing as t | ||
|
||
import click | ||
import globus_sdk | ||
|
||
from globus_cli.login_manager import LoginManager | ||
from globus_cli.parsing import command | ||
from globus_cli.termio import display | ||
from globus_cli.utils import LazyDict, is_uuid | ||
|
||
from ._common import DECORATED_SCOPE_FIELDS, BatchScopeStringResolver | ||
|
||
|
||
@command("show") | ||
@click.argument("SCOPE_ID_OR_STRING", type=str) | ||
@LoginManager.requires_login("auth") | ||
def show_command(login_manager: LoginManager, *, scope_id_or_string: str) -> None: | ||
"""Show a scope by ID or string.""" | ||
auth_client = login_manager.get_auth_client() | ||
|
||
if is_uuid(scope_id_or_string): | ||
show_scope_by_id(auth_client, scope_id_or_string) | ||
else: | ||
show_scope_by_string(auth_client, scope_id_or_string) | ||
|
||
|
||
def show_scope_by_id(auth_client: globus_sdk.AuthClient, scope_id: str) -> None: | ||
scope_resp = auth_client.get_scope(scope_id) | ||
|
||
decorate_scope_response(auth_client, scope_resp["scope"]) | ||
|
||
display( | ||
scope_resp, | ||
text_mode=display.RECORD, | ||
fields=DECORATED_SCOPE_FIELDS, | ||
response_key="scope", | ||
) | ||
|
||
|
||
def show_scope_by_string(auth_client: globus_sdk.AuthClient, scope_string: str) -> None: | ||
scope_resp = auth_client.get_scopes(scope_strings=[scope_string]) | ||
|
||
decorate_scope_response(auth_client, scope_resp["scopes"][0]) | ||
|
||
display( | ||
scope_resp, | ||
text_mode=display.RECORD, | ||
fields=DECORATED_SCOPE_FIELDS, | ||
response_key=lambda resp: resp["scopes"][0], | ||
) | ||
|
||
|
||
def decorate_scope_response( | ||
auth_client: globus_sdk.AuthClient, | ||
scope: dict[str, t.Any], | ||
) -> None: | ||
""" | ||
Decorates the dependent scopes of a get-scope response. | ||
|
||
Every dependent scope dict has a "scope_string" lazy-loader added to it that will | ||
resolve the scope string by querying globus auth (with batching and caching). | ||
""" | ||
dependent_scopes = scope.get("dependent_scopes") | ||
if not dependent_scopes: | ||
return | ||
|
||
# Create a batch resolver so that we resolve all dependent scope strings at once. | ||
dependent_scope_ids = [dependent["scope"] for dependent in dependent_scopes] | ||
resolver = BatchScopeStringResolver(auth_client, dependent_scope_ids) | ||
|
||
# Replace the dependent scopes with LazyDicts. | ||
scope["dependent_scopes"] = [LazyDict(dependent) for dependent in dependent_scopes] | ||
for dependent in scope["dependent_scopes"]: | ||
load_scope_string = functools.partial(resolver.resolve, dependent["scope"]) | ||
dependent.register_loader("scope_string", load_scope_string) |
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
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
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
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
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since
None
isn't the default, I'm surprised that it appears in the type annotation. Is this an artifact of a previous rev?