-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #768 from globus/an/add-globus-session-error-support
Add support for Globus Auth Requirements Error responses
- Loading branch information
Showing
10 changed files
with
1,200 additions
and
0 deletions.
There are no files selected for viewing
3 changes: 3 additions & 0 deletions
3
changelog.d/20230629_093104_ada_add_session_error_support.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,3 @@ | ||
* Add a new class ``GlobusAuthRequirementsError`` and utility functions to the | ||
experimental subpackage to support handling of the Globus Auth Requirements Error | ||
response format (:pr:`NUMBER`) |
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,139 @@ | ||
Auth Requirements Errors | ||
======================== | ||
|
||
Globus Auth Requirements Error is a response format that conveys to a client any | ||
modifications to a session (i.e., "boosting") that will be required | ||
to complete a particular request. | ||
|
||
The ``globus_sdk.experimental.auth_requirements_error`` module provides a | ||
number of tools to make it easier to identify and handle these errors when they occur. | ||
|
||
GlobusAuthRequirementsError | ||
--------------------------- | ||
|
||
The ``GlobusAuthRequirementsError`` class provides a model for working with Globus | ||
Auth Requirements Error responses. | ||
|
||
Services in the Globus ecosystem may need to communicate authorization requirements | ||
to their consumers. For example, a service may need to instruct clients to have the user | ||
consent to an additional scope, ``"foo"``. In such a case, ``GlobusAuthRequirementsError`` | ||
can provide serialization into the well-known Globus Auth Requirements Error format: | ||
|
||
.. code-block:: python | ||
from globus_sdk.experimental.auth_requirements_error import GlobusAuthRequirementsError | ||
error = GlobusAuthRequirementsError( | ||
code="ConsentRequired", | ||
authorization_parameters=GlobusAuthorizationParameters( | ||
required_scopes=["foo"], | ||
session_message="Missing required 'foo' consent", | ||
), | ||
) | ||
# Render a strict dictionary | ||
error.to_dict() | ||
If non-canonical fields are needed, the ``extra`` argument can be used to | ||
supply a dictionary of additional fields to include. Non-canonical fields present | ||
in the provided dictionary when calling ``from_dict()`` are stored similarly. | ||
You can include these fields in the rendered output dictionary | ||
by specifying ``include_extra=True`` when calling ``to_dict()``. | ||
|
||
.. code-block:: python | ||
from globus_sdk.experimental.auth_requirements_error import GlobusAuthRequirementsError | ||
error = GlobusAuthRequirementsError( | ||
code="ConsentRequired", | ||
authorization_parameters=GlobusAuthorizationParameters( | ||
required_scopes=["foo"], | ||
session_message="Missing required 'foo' consent", | ||
), | ||
extra={ | ||
"message": "Missing required 'foo' consent", | ||
"request_id": "WmMV97A1w", | ||
"required_scopes": ["foo"], | ||
"resource": "/transfer", | ||
}, | ||
) | ||
# Render a dictionary with extra fields | ||
error.to_dict(include_extra=True) | ||
These fields are stored by both the ``GlobusAuthRequirementsError`` and | ||
``GlobusAuthenticationParameters`` classes in an ``extra`` attribute. | ||
|
||
.. note:: | ||
|
||
Non-canonical fields in a Globus Auth Requirements Error are primarily intended | ||
to make it easier for services to provide backward-compatibile error responses | ||
to clients that have not adopted the Globus Auth Requirements Error format. Avoid | ||
using non-canonical fields for any data that should be generically understood by | ||
a consumer of the error response. | ||
|
||
Parsing Responses | ||
----------------- | ||
|
||
If you are writing a client to a Globus API, the ``auth_requirements_error`` subpackage | ||
provides utilities to detect legacy Globus Auth requirements error response | ||
formats and normalize them. | ||
|
||
To detect if a ``GlobusAPIError``, ``ErrorSubdocument``, or JSON response | ||
dictionary represents an error that can be converted to a Globus Auth | ||
Requirements Error, you can use, e.g.,: | ||
|
||
.. code-block:: python | ||
from globus_sdk.experimental import auth_requirements_error | ||
error_dict = { | ||
"code": "ConsentRequired", | ||
"message": "Missing required foo consent", | ||
} | ||
# The dict is not a Globus Auth Requirements Error, so `False` is returned. | ||
auth_requirements_error.utils.is_auth_requirements_error(error_dict) | ||
# The dict is not a Globus Auth Requirements Error and cannot be converted. | ||
auth_requirements_error.utils.to_auth_requirements_error(error_dict) # None | ||
error_dict = { | ||
"code": "ConsentRequired", | ||
"message": "Missing required foo consent", | ||
"required_scopes": ["urn:globus:auth:scope:transfer.api.globus.org:all[*foo]"], | ||
} | ||
auth_requirements_error.utils.is_auth_requirements_error(error_dict) # True | ||
auth_requirements_error.utils.to_auth_requirements_error( | ||
error_dict | ||
) # GlobusAuthRequirementsError | ||
.. note:: | ||
|
||
If a ``GlobusAPIError`` represents multiple errors that were returned in an | ||
array, ``to_auth_requirements_error()`` only returns the first error in that | ||
array that can be converted to the Globus Auth Requirements Error response format. | ||
In this case (and in general) it's preferable to use | ||
``to_auth_requirements_errors()`` (which also accepts a list of | ||
``GlobusAPIError``\ s, ``ErrorSubdocument``\ s, and JSON response dictionaries): | ||
|
||
.. code-block:: python | ||
auth_requirements_error.utils.to_auth_requirements_error( | ||
other_error | ||
) # GlobusAuthRequirementsError | ||
auth_requirements_error.utils.to_auth_requirements_errors( | ||
[other_error] | ||
) # [GlobusAuthRequirementsError, ...] | ||
Notes | ||
----- | ||
|
||
``GlobusAuthRequirementsError`` enforces types strictly when parsing a Globus | ||
Auth Requirements Error response dictionary, and will raise a ``ValueError`` if a | ||
supported field is supplied with a value of the wrong type. | ||
|
||
``GlobusAuthRequirementsError`` does not attempt to mimic or itself enforce | ||
any logic specific to the Globus Auth service with regard to what represents a valid | ||
combination of fields (e.g., ``session_required_mfa`` requires either | ||
``session_required_identities`` or ``session_required_single_domain`` | ||
in order to be properly handled). |
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 |
---|---|---|
|
@@ -16,4 +16,5 @@ Globus SDK Experimental Components | |
:caption: Contents | ||
:maxdepth: 1 | ||
|
||
auth_requirements_errors | ||
scope_parser |
21 changes: 21 additions & 0 deletions
21
src/globus_sdk/experimental/auth_requirements_error/__init__.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,21 @@ | ||
from ._auth_requirements_error import ( | ||
GlobusAuthorizationParameters, | ||
GlobusAuthRequirementsError, | ||
) | ||
from ._functional_api import ( | ||
has_auth_requirements_errors, | ||
is_auth_requirements_error, | ||
to_auth_requirements_error, | ||
to_auth_requirements_errors, | ||
) | ||
from ._validators import ValidationError | ||
|
||
__all__ = [ | ||
"ValidationError", | ||
"GlobusAuthRequirementsError", | ||
"GlobusAuthorizationParameters", | ||
"to_auth_requirements_error", | ||
"to_auth_requirements_errors", | ||
"is_auth_requirements_error", | ||
"has_auth_requirements_errors", | ||
] |
114 changes: 114 additions & 0 deletions
114
src/globus_sdk/experimental/auth_requirements_error/_auth_requirements_error.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,114 @@ | ||
from __future__ import annotations | ||
|
||
import typing as t | ||
|
||
from . import _serializable, _validators | ||
|
||
|
||
class GlobusAuthorizationParameters(_serializable.Serializable): | ||
""" | ||
Represents authorization parameters that can be used to instruct a client | ||
which additional authorizations are needed in order to complete a request. | ||
:ivar session_message: A message to be displayed to the user. | ||
:vartype session_message: str, optional | ||
:ivar session_required_identities: A list of identities required for the | ||
session. | ||
:vartype session_required_identities: list of str, optional | ||
:ivar session_required_policies: A list of policies required for the | ||
session. | ||
:vartype session_required_policies: list of str, optional | ||
:ivar session_required_single_domain: A list of domains required for the | ||
session. | ||
:vartype session_required_single_domain: list of str, optional | ||
:ivar session_required_mfa: Whether MFA is required for the session. | ||
:vartype session_required_mfa: bool, optional | ||
:ivar required_scopes: A list of scopes for which consent is required. | ||
:vartype required_scopes: list of str, optional | ||
:ivar extra: A dictionary of additional fields that were provided. May | ||
be used for forward/backward compatibility. | ||
:vartype extra: dict | ||
""" | ||
|
||
def __init__( | ||
self, | ||
*, | ||
session_message: str | None = None, | ||
session_required_identities: list[str] | None = None, | ||
session_required_policies: list[str] | None = None, | ||
session_required_single_domain: list[str] | None = None, | ||
session_required_mfa: bool | None = None, | ||
required_scopes: list[str] | None = None, | ||
extra: dict[str, t.Any] | None = None, | ||
): | ||
self.session_message = _validators.opt_str("session_message", session_message) | ||
self.session_required_identities = _validators.opt_str_list( | ||
"session_required_identities", session_required_identities | ||
) | ||
self.session_required_policies = _validators.opt_str_list( | ||
"session_required_policies", session_required_policies | ||
) | ||
self.session_required_single_domain = _validators.opt_str_list( | ||
"session_required_single_domain", session_required_single_domain | ||
) | ||
self.session_required_mfa = _validators.opt_bool( | ||
"session_required_mfa", session_required_mfa | ||
) | ||
self.required_scopes = _validators.opt_str_list( | ||
"required_scopes", required_scopes | ||
) | ||
self.extra = extra or {} | ||
|
||
# Enforce that the error contains at least one of the fields we expect | ||
requires_at_least_one = [ | ||
name for name in self._supported_fields() if name != "session_message" | ||
] | ||
if all( | ||
getattr(self, field_name) is None for field_name in requires_at_least_one | ||
): | ||
raise _validators.ValidationError( | ||
"Must include at least one supported authorization parameter: " | ||
+ ", ".join(requires_at_least_one) | ||
) | ||
|
||
|
||
class GlobusAuthRequirementsError(_serializable.Serializable): | ||
""" | ||
Represents a Globus Auth Requirements Error. | ||
A Globus Auth Requirements Error is a class of error that is returned by Globus | ||
services to indicate that additional authorization is required in order to complete | ||
a request and contains information that can be used to request the appropriate | ||
authorization. | ||
:ivar code: The error code for this error. | ||
:vartype code: str | ||
:ivar authorization_parameters: The authorization parameters for this error. | ||
:vartype authorization_parameters: GlobusAuthorizationParameters | ||
:ivar extra: A dictionary of additional fields that were provided. May | ||
be used for forward/backward compatibility. | ||
:vartype extra: dict | ||
""" | ||
|
||
def __init__( | ||
self, | ||
code: str, | ||
authorization_parameters: dict[str, t.Any] | GlobusAuthorizationParameters, | ||
*, | ||
extra: dict[str, t.Any] | None = None, | ||
): | ||
self.code = _validators.str_("code", code) | ||
self.authorization_parameters = _validators.instance_or_dict( | ||
"authorization_parameters", | ||
authorization_parameters, | ||
GlobusAuthorizationParameters, | ||
) | ||
self.extra = extra or {} |
Oops, something went wrong.