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

Add scope to oauth2_get_dependent_tokens #965

Merged
merged 3 commits into from
Mar 19, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Added
~~~~~

- ``ConfidentialAppAuthClient.oauth2_get_dependent_tokens`` now supports the
``scope`` parameter as a string or iterable of strings. (:pr:`NUMBER`)
46 changes: 41 additions & 5 deletions src/globus_sdk/services/auth/client/confidential_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,12 @@ def oauth2_get_dependent_tokens(
token: str,
*,
refresh_tokens: bool = False,
scope: str | t.Iterable[str] | utils.MissingType = utils.MISSING,
additional_params: dict[str, t.Any] | None = None,
) -> OAuthDependentTokenResponse:
"""
Does a `Dependent Token Grant
<https://docs.globus.org/api/auth/reference/#dependent_token_grant_post_v2_oauth2_token>`_
against Globus Auth.
Fetch Dependent Tokens from Globus Auth.

This exchanges a token given to this client for a new set of tokens
which give it access to resource servers on which it depends.
This grant type is intended for use by Resource Servers playing out the
Expand All @@ -211,11 +211,45 @@ def oauth2_get_dependent_tokens(
their relationship). As long as that is the case, Service A can use
this Grant to get those "Dependent" or "Downstream" tokens for Service B.

:param token: A Globus Access Token as a string
:param token: An access token as a string
:param refresh_tokens: When True, request dependent refresh tokens in addition
to access tokens. [Default: ``False``]
:param scope: The scope or scopes of the dependent tokens which are being
requested. Applications are recommended to provide this string to ensure
that they are receiving the tokens they expect. If omitted, all available
dependent tokens will be returned.
:param additional_params: Additional parameters to include in the request body
"""

.. tab-set::

.. tab-item:: Example Usage

Given a token, getting a dependent token for Globus Groups might
look like the following:

.. code-block:: python

ac = globus_sdk.ConfidentialAppAuthClient(CLIENT_ID, CLIENT_SECRET)
dependent_token_data = ac.oauth2_get_dependent_tokens(
"<token_string>",
scope="urn:globus:auth:scope:groups.api.globus.org:view_my_groups_and_memberships",
)

group_token_data = dependent_token_data.by_resource_server["groups.api.globus.org"]
group_token = group_token_data["access_token"]

.. tab-item:: Example Response Data

.. expandtestfixture:: auth.oauth2_get_dependent_tokens
:case: groups

.. tab-item:: API Info

``POST /v2/oauth2/token``

.. extdoclink:: Dependent Token Grant
:ref: auth/reference/##dependent_token_grant_post_v2oauth2token
""" # noqa: E501
log.info("Getting dependent tokens from access token")
log.debug(f"additional_params={additional_params}")
form_data = {
Expand All @@ -227,6 +261,8 @@ def oauth2_get_dependent_tokens(
# back to the user than the OAuth2 spec wording
if refresh_tokens:
form_data["access_type"] = "offline"
if not isinstance(scope, utils.MissingType):
form_data["scope"] = " ".join(utils.safe_strseq_iter(scope))
if additional_params:
form_data.update(additional_params)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import urllib.parse

import pytest

from globus_sdk._testing import get_last_request, load_response


Expand Down Expand Up @@ -45,3 +47,28 @@ def test_oauth2_get_dependent_tokens_with_refresh_token(auth_client):
assert body != ""
parsed_body = urllib.parse.parse_qs(body)
assert parsed_body["access_type"] == ["offline"]


@pytest.mark.parametrize(
"scope_arg, expect_value",
[(None, None), ("scope1", "scope1"), (("scope1", "scope2"), "scope1 scope2")],
)
def test_oauth2_get_dependent_tokens_scope_string_param(
auth_client, scope_arg, expect_value
):
load_response(auth_client.oauth2_get_dependent_tokens, case="groups")

add_args = {}
if scope_arg is not None:
add_args["scope"] = scope_arg
auth_client.oauth2_get_dependent_tokens("dummy_token", **add_args)

last_req = get_last_request()
assert last_req.body
body = last_req.body
assert body != ""
parsed_body = urllib.parse.parse_qs(body)
if expect_value is None:
assert "scope" not in parsed_body
else:
assert parsed_body["scope"] == [expect_value]