Skip to content

Commit

Permalink
Move login flows (#1057)
Browse files Browse the repository at this point in the history
* Promote login flow managers from 'experimental'

* Add LoginFlowManagers to docs

* Fix import path

* Cosmetic changes

* Only allow native apps to use localserver login flow manager

* Fix raises class references
derek-globus authored Sep 20, 2024

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
1 parent 4afc1f9 commit f1f450c
Showing 24 changed files with 330 additions and 176 deletions.
8 changes: 8 additions & 0 deletions changelog.d/20240918_120618_derek_move_login_flows.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

Changed
~~~~~~~

- LoginFlowManagers have been moved from ``globus_sdk.experimental.login_flow_managers``
to ``globus_sdk.login_flows``. (:pr:`NUMBER`)


1 change: 1 addition & 0 deletions docs/authorization/index.rst
Original file line number Diff line number Diff line change
@@ -8,4 +8,5 @@ Components of the Globus SDK which handle application authorization.

globus_authorizers
scopes_and_consents/index
login_flows
gare
95 changes: 95 additions & 0 deletions docs/authorization/login_flows.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@

Login Flow Managers
===================

.. currentmodule:: globus_sdk.login_flows

This page provides references for the LoginFlowManager abstract class and some concrete
implementations.

A login flow manager is a class responsible for driving a user through a login flow,
with the ultimate goal of obtaining tokens. The tokens are required to make
requests against any Globus services.

Interface
---------

.. autoclass:: LoginFlowManager
:members:

Command Line
------------

As the name might suggest, a CommandLineLoginFlowManager drives user logins through
the command line (stdin/stdout). When run, the manager will print a URL to the console
then prompt a user to navigate to that URL and enter the resulting auth code
back into the terminal.

Example Code:

.. code-block:: pycon
>>> from globus_sdk import NativeAppAuthClient
>>> from globus_sdk.scopes import TransferScopes
>>> from globus_sdk.login_flows import CommandLineLoginFlowManager
>>> login_client = NativeAppAuthClient(client_id=client_id)
>>> manager = CommandLineLoginFlowManager(login_client)
>>>
>>> token_response = manager.run_login_flow(
... GlobusAuthorizationParameters(required_scopes=[TransferScopes.all])
... )
Please authenticate with Globus here:
-------------------------------------
https://auth.globus.org/v2/oauth2/authorize?cli...truncated...
-------------------------------------
Enter the resulting Authorization Code here:
.. autoclass:: CommandLineLoginFlowManager
:members:
:member-order: bysource
:show-inheritance:

Local Server
------------

A LocalServerLoginFlowManager drives more automated, but less portable, login flows
compared with its command line counterpart. When run, rather than printing the
authorization URL, the manager will open it in the user's default browser. Alongside
this, the manager will start a local web server to receive the auth code upon completion
of the login flow.

This provides a more user-friendly login experience as there is no manually copy/pasting
of links and codes. It also requires however that the python process be running in an
environment with access to a supported browser. As such, this flow is not suitable for
headless environments (e.g., while ssh-ed into a cluster node).

.. note::

This login manager is only supported for native clients.


Example Usage:

.. code-block:: pycon
>>> from globus_sdk import NativeAppAuthClient
>>> from globus_sdk.scopes import TransferScopes
>>> from globus_sdk.login_flows import LocalServerLoginFlowManager
>>> login_client = NativeAppAuthClient(client_id=client_id)
>>> manager = LocalServerLoginFlowManager(login_client)
>>>
>>> token_response = manager.run_login_flow(
... GlobusAuthorizationParameters(required_scopes=[TransferScopes.all])
... )
.. autoclass:: LocalServerLoginFlowManager
:members:
:member-order: bysource
:show-inheritance:

.. autoexception:: LocalServerLoginError

.. autoexception:: LocalServerEnvironmentalLoginError
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -54,7 +54,7 @@ namespaces = false

[tool.setuptools.package-data]
globus_sdk = ["py.typed"]
"globus_sdk.experimental.login_flow_manager.local_server_login_flow_manager.html_files" = ["*.html"]
"globus_sdk.login_flows.local_server_login_flow_manager.html_files" = ["*.html"]

[tool.setuptools.dynamic.version]
attr = "globus_sdk.__version__"
2 changes: 1 addition & 1 deletion src/globus_sdk/experimental/globus_app/_types.py
Original file line number Diff line number Diff line change
@@ -5,8 +5,8 @@

from globus_sdk import AuthLoginClient
from globus_sdk._types import UUIDLike
from globus_sdk.experimental.login_flow_manager import LoginFlowManager
from globus_sdk.experimental.tokenstorage import TokenStorage
from globus_sdk.login_flows import LoginFlowManager

if sys.version_info < (3, 8):
from typing_extensions import Protocol, runtime_checkable
10 changes: 5 additions & 5 deletions src/globus_sdk/experimental/globus_app/config.py
Original file line number Diff line number Diff line change
@@ -4,17 +4,17 @@
import typing as t

from globus_sdk.config import get_environment_name
from globus_sdk.experimental.login_flow_manager import (
CommandLineLoginFlowManager,
LocalServerLoginFlowManager,
LoginFlowManager,
)
from globus_sdk.experimental.tokenstorage import (
JSONTokenStorage,
MemoryTokenStorage,
SQLiteTokenStorage,
TokenStorage,
)
from globus_sdk.login_flows import (
CommandLineLoginFlowManager,
LocalServerLoginFlowManager,
LoginFlowManager,
)

from ._types import (
LoginFlowManagerProvider,
5 changes: 1 addition & 4 deletions src/globus_sdk/experimental/globus_app/user_app.py
Original file line number Diff line number Diff line change
@@ -9,11 +9,8 @@
NativeAppAuthClient,
)
from globus_sdk._types import ScopeCollectionType, UUIDLike
from globus_sdk.experimental.login_flow_manager import (
CommandLineLoginFlowManager,
LoginFlowManager,
)
from globus_sdk.gare import GlobusAuthorizationParameters
from globus_sdk.login_flows import CommandLineLoginFlowManager, LoginFlowManager

from ._types import LoginFlowManagerProvider
from .app import GlobusApp
37 changes: 37 additions & 0 deletions src/globus_sdk/experimental/login_flow_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from __future__ import annotations

import sys
import typing as t

__all__ = (
"CommandLineLoginFlowManager",
"LocalServerLoginFlowManager",
"LoginFlowManager",
)

# legacy aliases
# (when accessed, these will emit deprecation warnings)
if t.TYPE_CHECKING:
from globus_sdk.login_flows import (
CommandLineLoginFlowManager,
LocalServerLoginFlowManager,
LoginFlowManager,
)
else:

def __getattr__(name: str) -> t.Any:
import globus_sdk.login_flows as login_flows_module
from globus_sdk.exc import warn_deprecated

warn_deprecated(
"'globus_sdk.experimental.login_flow_manager' has been renamed to "
"'globus_sdk.login_flows'. "
f"Importing '{name}' from `globus_sdk.experimental` is deprecated. "
f"Use `globus_sdk.login_flows.{name}` instead."
)

value = getattr(login_flows_module, name, None)
if value is None:
raise AttributeError(f"module {__name__} has no attribute {name}")
setattr(sys.modules[__name__], name, value)
return value

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
from .command_line_login_flow_manager import CommandLineLoginFlowManager
from .local_server_login_flow_manager import LocalServerLoginFlowManager
from .local_server_login_flow_manager import (
LocalServerEnvironmentalLoginError,
LocalServerLoginError,
LocalServerLoginFlowManager,
)
from .login_flow_manager import LoginFlowManager

__all__ = [
"LoginFlowManager",
"CommandLineLoginFlowManager",
"LocalServerLoginError",
"LocalServerEnvironmentalLoginError",
"LocalServerLoginFlowManager",
"LoginFlowManager",
]
Original file line number Diff line number Diff line change
@@ -19,17 +19,22 @@

class CommandLineLoginFlowManager(LoginFlowManager):
"""
A ``CommandLineLoginFlowManager`` is a ``LoginFlowManager`` that uses the command
line for interacting with the user during its interactive login flows.
Example usage:
>>> login_client = globus_sdk.NativeAppAuthClient(...)
>>> login_flow_manager = CommandLineLoginFlowManager(login_client)
>>> scopes = [globus_sdk.scopes.TransferScopes.all]
>>> auth_params = GlobusAuthorizationParameters(required_scopes=scopes)
>>> tokens = login_flow_manager.run_login_flow(auth_params)
A login flow manager which drives authorization-code token grants through the
command line.
:param AuthLoginClient login_client: The client that will be making Globus
Auth API calls required for a login flow.
.. note::
If this client is a :class:`globus_sdk.ConfidentialAppAuthClient`, an
explicit `redirect_uri` param is required.
:param str redirect_uri: The redirect URI to use for the login flow. When the
`login_client` is a native client, this defaults to a Globus-hosted URL.
:param bool request_refresh_tokens: A signal of whether refresh tokens are expected
to be requested, in addition to access tokens.
:param str native_prefill_named_grant: A string to prefill in a Native App login
flow. This value is only used if the `login_client` is a native client.
"""

def __init__(
@@ -40,18 +45,6 @@ def __init__(
request_refresh_tokens: bool = False,
native_prefill_named_grant: str | None = None,
) -> None:
"""
:param login_client: The ``AuthLoginClient`` that will be making the Globus
Auth API calls needed for the authentication flow. Note that this
must either be a NativeAppAuthClient or a templated
ConfidentialAppAuthClient, standard ConfidentialAppAuthClients cannot
use the web auth-code flow.
:param redirect_uri: The redirect URI to use for the login flow. Defaults to
a globus-hosted helper web auth-code URI for NativeAppAuthClients.
:param request_refresh_tokens: Control whether refresh tokens will be requested.
:param native_prefill_named_grant: The named grant label to prefill on the
consent page when using a NativeAppAuthClient.
"""
super().__init__(
login_client,
request_refresh_tokens=request_refresh_tokens,
@@ -73,15 +66,13 @@ def for_globus_app(
cls, app_name: str, login_client: AuthLoginClient, config: GlobusAppConfig
) -> CommandLineLoginFlowManager:
"""
Create a ``CommandLineLoginFlowManager`` for a given ``GlobusAppConfig``.
:param app_name: The name of the app to use for prefilling the named grant in
native auth flows.
:param login_client: The ``AuthLoginClient`` to use to drive Globus Auth flows.
:param config: A ``GlobusAppConfig`` to configure the login flow.
:returns: A ``CommandLineLoginFlowManager`` instance.
:raises: GlobusSDKUsageError if a login_redirect_uri is not set on the config
but a ConfidentialAppAuthClient is used.
Create a ``CommandLineLoginFlowManager`` for use in a GlobusApp.
:param app_name: The name of the app. Will be prefilled in native auth flows.
:param login_client: A client used to make Globus Auth API calls.
:param config: A GlobusApp-bounded object used to configure login flow manager.
:raises GlobusSDKUsageError: if login_redirect_uri is not set on the config
but a ConfidentialAppAuthClient is supplied.
"""
return cls(
login_client,
@@ -128,6 +119,8 @@ def print_authorize_url(self, authorize_url: str) -> None:
def prompt_for_code(self) -> str:
"""
Prompt the user to enter an authorization code.
:returns: The authorization code entered by the user.
"""
code_prompt = "Enter the resulting Authorization Code here: "
return input(code_prompt).strip()
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .errors import LocalServerEnvironmentalLoginError, LocalServerLoginError
from .local_server_login_flow_manager import LocalServerLoginFlowManager

__all__ = [
"LocalServerLoginError",
"LocalServerEnvironmentalLoginError",
"LocalServerLoginFlowManager",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class LocalServerLoginError(Exception):
"""An error raised during a LocalServerLoginFlowManager's run."""


class LocalServerEnvironmentalLoginError(LocalServerLoginError):
"""
Error raised when a local server login flow fails to start due to incompatible
environment conditions (e.g., a remote session or text-only browser).
"""
Original file line number Diff line number Diff line change
@@ -13,16 +13,19 @@
import sys
import time
import typing as t
from datetime import timedelta
from http.server import BaseHTTPRequestHandler, HTTPServer
from string import Template
from urllib.parse import parse_qsl, urlparse

from .errors import LocalServerLoginError

if sys.version_info >= (3, 9):
import importlib.resources as importlib_resources
else: # Python < 3.9
import importlib_resources

import globus_sdk.experimental.login_flow_manager.local_server_login_flow_manager.html_files as html_files # noqa: E501
import globus_sdk.login_flows.local_server_login_flow_manager.html_files as html_files # noqa: E501

_IS_WINDOWS = os.name == "nt"

@@ -33,19 +36,50 @@
)


class LocalServerError(Exception):
class RedirectHandler(BaseHTTPRequestHandler):
"""
Error class for errors raised by the local server when using a
LocalServerLoginFlowManager
BaseHTTPRequestHandler to be used by RedirectHTTPServer.
Displays the RedirectHTTPServer's html_template and parses auth_code out of
the redirect url.
"""

server: RedirectHTTPServer

def do_GET(self) -> None:
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()

html_template = self.server.html_template
query_params = dict(parse_qsl(urlparse(self.path).query))
code = query_params.get("code")
if code:
self.wfile.write(
html_template.substitute(
post_login_message="", login_result="Login successful"
).encode("utf-8")
)
self.server.return_code(code)
else:
msg = query_params.get("error_description", query_params.get("error"))

self.wfile.write(
html_template.substitute(
post_login_message=msg, login_result="Login failed"
).encode("utf-8")
)

self.server.return_code(LocalServerLoginError(msg))


class RedirectHTTPServer(HTTPServer):
"""
HTTPServer that accepts an html_template to be displayed to the user
An HTTPServer which accepts an HTML `Template` to be displayed to the user
and uses a Queue to receive an auth_code from its RequestHandler.
"""

WAIT_TIMEOUT = timedelta(minutes=15)

def __init__(
self,
server_address: tuple[str, int],
@@ -72,7 +106,7 @@ def return_code(self, code: str | BaseException) -> None:
def wait_for_code(self) -> str | BaseException:
# Windows needs special handling as blocking prevents ctrl-c interrupts
if _IS_WINDOWS:
deadline = time.time() + 3600
deadline = time.time() + self.WAIT_TIMEOUT.total_seconds()
while time.time() < deadline:
try:
return self._auth_code_queue.get()
@@ -83,40 +117,4 @@ def wait_for_code(self) -> str | BaseException:
return self._auth_code_queue.get(block=True, timeout=3600)
except queue.Empty:
pass
raise LocalServerError("Login timed out. Please try again.")


class RedirectHandler(BaseHTTPRequestHandler):
"""
BaseHTTPRequestHandler to be used by RedirectHTTPServer.
Displays the RedirectHTTPServer's html_template and parses auth_code out of
the redirect url.
"""

server: RedirectHTTPServer

def do_GET(self) -> None:
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()

html_template = self.server.html_template
query_params = dict(parse_qsl(urlparse(self.path).query))
code = query_params.get("code")
if code:
self.wfile.write(
html_template.substitute(
post_login_message="", login_result="Login successful"
).encode("utf-8")
)
self.server.return_code(code)
else:
msg = query_params.get("error_description", query_params.get("error"))

self.wfile.write(
html_template.substitute(
post_login_message=msg, login_result="Login failed"
).encode("utf-8")
)

self.server.return_code(LocalServerError(msg))
raise LocalServerLoginError("Login timed out. Please try again.")
Original file line number Diff line number Diff line change
@@ -7,18 +7,17 @@
from contextlib import contextmanager
from string import Template

from globus_sdk import AuthLoginClient, GlobusSDKUsageError, OAuthTokenResponse
from globus_sdk.experimental.login_flow_manager.login_flow_manager import (
LoginFlowManager,
from globus_sdk import (
AuthLoginClient,
GlobusSDKUsageError,
NativeAppAuthClient,
OAuthTokenResponse,
)
from globus_sdk.gare import GlobusAuthorizationParameters
from globus_sdk.login_flows.login_flow_manager import LoginFlowManager

from ._local_server import (
DEFAULT_HTML_TEMPLATE,
LocalServerError,
RedirectHandler,
RedirectHTTPServer,
)
from .errors import LocalServerEnvironmentalLoginError, LocalServerLoginError
from .local_server import DEFAULT_HTML_TEMPLATE, RedirectHandler, RedirectHTTPServer

if t.TYPE_CHECKING:
from globus_sdk.experimental.globus_app import GlobusAppConfig
@@ -31,22 +30,13 @@
BROWSER_BLACKLIST = ["lynx", "www-browser", "links", "elinks", "w3m"]


class LocalServerLoginFlowError(BaseException):
"""
Error class for errors raised due to inability to run a local server login flow
due to known failure conditions such as remote sessions or text-only browsers.
Catching this should be sufficient to detect cases where one should fallback
to a CommandLineLoginFlowManager
"""


def _check_remote_session() -> None:
"""
Try to check if this is being run during a remote session, if so
raise LocalServerLoginFlowError
"""
if bool(os.environ.get("SSH_TTY", os.environ.get("SSH_CONNECTION"))):
raise LocalServerLoginFlowError(
raise LocalServerEnvironmentalLoginError(
"Cannot use LocalServerLoginFlowManager in a remote session"
)

@@ -66,34 +56,42 @@ def _open_webbrowser(url: str) -> None:
# https://github.com/python/cpython/issues/82828
browser_name = browser._name
else:
raise LocalServerLoginFlowError("Unable to determine local browser name.")
raise LocalServerEnvironmentalLoginError(
"Unable to determine local browser name."
)

if browser_name in BROWSER_BLACKLIST:
raise LocalServerLoginFlowError(
raise LocalServerEnvironmentalLoginError(
"Cannot use LocalServerLoginFlowManager with "
f"text-only browser '{browser_name}'"
)

if not browser.open(url, new=1):
raise LocalServerLoginFlowError(f"Failed to open browser '{browser_name}'")
raise LocalServerEnvironmentalLoginError(
f"Failed to open browser '{browser_name}'"
)
except webbrowser.Error as exc:
raise LocalServerLoginFlowError("Failed to open browser") from exc
raise LocalServerEnvironmentalLoginError("Failed to open browser") from exc


class LocalServerLoginFlowManager(LoginFlowManager):
"""
A ``LocalServerLoginFlowManager`` is a ``LoginFlowManager`` that uses a locally
hosted server to automatically receive the auth code from Globus auth after the
user has authenticated.
Example usage:
>>> login_client = globus_sdk.NativeAppAuthClient(...)
>>> login_flow_manager = LocalServerLoginFlowManager(login_client)
>>> scopes = [globus_sdk.scopes.TransferScopes.all]
>>> auth_params = GlobusAuthorizationParameters(required_scopes=scopes)
>>> tokens = login_flow_manager.run_login_flow(auth_params)
A login flow manager which uses a locally hosted server to drive authentication-code
token grants. The local server is used as the authorization redirect URI,
automatically receiving the auth code from Globus Auth after authentication/consent.
:param AuthLoginClient login_client: The client that will be making Globus
Auth API calls required for a login flow.
:param bool request_refresh_tokens: A signal of whether refresh tokens are expected
to be requested, in addition to access tokens.
:param str native_prefill_named_grant: A string to prefill in a Native App login
flow. This value is only used if the `login_client` is a native client.
:param Template html_template: Optional HTML Template to be populated with the
values login_result and post_login_message and displayed to the user. A simple
default is supplied if not provided which informs the user that the login was
successful and that they may close the browser window.
:param tuple[str, int] server_address: Optional tuple of the form (host, port) to
specify an address to run the local server at. Defaults to ("127.0.0.1", 0).
"""

def __init__(
@@ -102,23 +100,9 @@ def __init__(
*,
request_refresh_tokens: bool = False,
native_prefill_named_grant: str | None = None,
server_address: tuple[str, int] = ("127.0.0.1", 0),
server_address: tuple[str, int] = ("localhost", 0),
html_template: Template = DEFAULT_HTML_TEMPLATE,
) -> None:
"""
:param login_client: The ``AuthLoginClient`` that will be making the Globus
Auth API calls needed for the authentication flow. Note that this
must either be a NativeAppAuthClient or a templated
ConfidentialAppAuthClient, standard ConfidentialAppAuthClients cannot
use the web auth-code flow.
:param request_refresh_tokens: Control whether refresh tokens will be requested.
:param native_prefill_named_grant: The named grant label to prefill on the
consent page when using a NativeAppAuthClient.
:param html_template: Optional HTML Template to be populated with the values
login_result and post_login_message and displayed to the user.
:param server_address: Optional tuple of the form (host, port) to specify an
address to run the local server at.
"""
super().__init__(
login_client,
request_refresh_tokens=request_refresh_tokens,
@@ -132,20 +116,25 @@ def for_globus_app(
cls, app_name: str, login_client: AuthLoginClient, config: GlobusAppConfig
) -> LocalServerLoginFlowManager:
"""
Create a ``LocalServerLoginFlowManager`` for a given ``GlobusAppConfig``.
:param app_name: The name of the app to use for prefilling the named grant,.
:param login_client: The ``AuthLoginClient`` to use to drive Globus Auth flows.
:param config: A ``GlobusAppConfig`` to configure the login flow.
:returns: A ``LocalServerLoginFlowManager`` instance.
:raises: GlobusSDKUsageError if a custom login_redirect_uri is defined in
the config.
Create a ``LocalServerLoginFlowManager`` for use in a GlobusApp.
:param app_name: The name of the app. Will be prefilled in native auth flows.
:param login_client: A client used to make Globus Auth API calls.
:param config: A GlobusApp-bounded object used to configure login flow manager.
:raises GlobusSDKUsageError: if app config is incompatible with the manager.
"""
if config.login_redirect_uri:
# A "local server" relies on the user being redirected back to the server
# running on the local machine, so it can't use a custom redirect URI.
msg = "Cannot define a custom redirect_uri for LocalServerLoginFlowManager."
raise GlobusSDKUsageError(msg)
if not isinstance(login_client, NativeAppAuthClient):
# Globus Auth has special provisions for native clients which allow implicit
# redirect url grant to localhost:<any-port>. This is required for the
# LocalServerLoginFlowManager to work and is not reproducible in
# confidential clients.
msg = "LocalServerLoginFlowManager is only supported for Native Apps."
raise GlobusSDKUsageError(msg)

return cls(
login_client,
@@ -163,6 +152,11 @@ def run_login_flow(
:param auth_parameters: ``GlobusAuthorizationParameters`` passed through
to the authentication flow to control how the user will authenticate.
:raises LocalServerEnvironmentalLoginError: If the local server login flow
cannot be run due to known failure conditions such as remote sessions or
text-only browsers.
:raises LocalServerLoginError: If the local server login flow fails for any
reason.
"""
_check_remote_session()

@@ -179,7 +173,7 @@ def run_login_flow(

if isinstance(auth_code, BaseException):
msg = f"Authorization failed with unexpected error:\n{auth_code}."
raise LocalServerError(msg)
raise LocalServerLoginError(msg)

# get and return tokens
return self.login_client.oauth2_exchange_code_for_tokens(auth_code)
Original file line number Diff line number Diff line change
@@ -14,9 +14,18 @@

class LoginFlowManager(metaclass=abc.ABCMeta):
"""
A ``LoginFlowManager`` is an abstract superclass for subclasses that manage
interactive login flows with a user in order to authenticate with Globus Auth
and obtain tokens.
The abstract base class defining the interface for managing login flows.
Implementing classes must supply a ``run_login_flow`` method.
Utility functions starting an authorization-code grant flow and getting an
authorization-code URL are provided on the class.
:ivar AuthLoginClient login_client: A native or confidential login client to be
used by the login flow manager.
:ivar bool request_refresh_tokens: A signal of whether refresh tokens are expected
to be requested, in addition to access tokens.
:ivar str native_prefill_named_grant: A string to prefill in a Native App login
flow. This value is only to be used if the `login_client` is a native client.
"""

def __init__(
@@ -26,13 +35,6 @@ def __init__(
request_refresh_tokens: bool = False,
native_prefill_named_grant: str | None = None,
) -> None:
"""
:param login_client: The client to use for login flows.
:param request_refresh_tokens: Control whether refresh tokens will be requested.
:param native_prefill_named_grant: The name of a prefill in a Native App login
flow. This value will be ignored if the login_client is not a
NativeAppAuthClient.
"""
if not isinstance(login_client, NativeAppAuthClient) and not isinstance(
login_client, ConfidentialAppAuthClient
):
@@ -94,7 +96,7 @@ def run_login_flow(
auth_parameters: GlobusAuthorizationParameters,
) -> OAuthTokenResponse:
"""
Run an interactive login flow to get tokens for the user.
Run a login flow to get tokens for a user.
:param auth_parameters: ``GlobusAuthorizationParameters`` passed through
to the authentication flow to control how the user will authenticate.
Empty file.
Original file line number Diff line number Diff line change
@@ -2,11 +2,11 @@

from globus_sdk import ConfidentialAppAuthClient, NativeAppAuthClient
from globus_sdk._testing import load_response
from globus_sdk.experimental.login_flow_manager import (
from globus_sdk.gare import GlobusAuthorizationParameters
from globus_sdk.login_flows import (
CommandLineLoginFlowManager,
LocalServerLoginFlowManager,
)
from globus_sdk.gare import GlobusAuthorizationParameters


def _mock_input(s):
@@ -112,7 +112,7 @@ def wait_for_code(self):


_LOCAL_SERVER_MODULE = (
"globus_sdk.experimental.login_flow_manager.local_server_login_flow_manager."
"globus_sdk.login_flows.local_server_login_flow_manager."
"local_server_login_flow_manager"
)

10 changes: 5 additions & 5 deletions tests/unit/experimental/globus_app/test_globus_app.py
Original file line number Diff line number Diff line change
@@ -24,18 +24,18 @@
RefreshTokenAuthorizerFactory,
UserApp,
)
from globus_sdk.experimental.login_flow_manager import (
CommandLineLoginFlowManager,
LocalServerLoginFlowManager,
LoginFlowManager,
)
from globus_sdk.experimental.tokenstorage import (
JSONTokenStorage,
MemoryTokenStorage,
SQLiteTokenStorage,
TokenStorageData,
)
from globus_sdk.gare import GlobusAuthorizationParameters
from globus_sdk.login_flows import (
CommandLineLoginFlowManager,
LocalServerLoginFlowManager,
LoginFlowManager,
)
from globus_sdk.scopes import AuthScopes, Scope
from globus_sdk.services.auth import OAuthTokenResponse

11 changes: 11 additions & 0 deletions tests/unit/experimental/test_legacy_support.py
Original file line number Diff line number Diff line change
@@ -9,10 +9,21 @@
can be deleted.
"""

import pytest


def test_scope_importable_from_experimental():
from globus_sdk.experimental.scope_parser import ( # noqa: F401
Scope,
ScopeCycleError,
ScopeParseError,
)


def test_login_flow_manager_importable_from_experimental():
with pytest.warns(DeprecationWarning):
from globus_sdk.experimental.login_flow_manager import ( # noqa: F401
CommandLineLoginFlowManager,
LocalServerLoginFlowManager,
LoginFlowManager,
)
Empty file.
Original file line number Diff line number Diff line change
@@ -2,9 +2,9 @@

import pytest

from globus_sdk.experimental.login_flow_manager.local_server_login_flow_manager._local_server import ( # noqa: E501
from globus_sdk.login_flows import LocalServerLoginError
from globus_sdk.login_flows.local_server_login_flow_manager.local_server import ( # noqa: E501
DEFAULT_HTML_TEMPLATE,
LocalServerError,
RedirectHandler,
RedirectHTTPServer,
)
@@ -21,8 +21,8 @@ def test_default_html_template_contains_expected_text():
"url,expected_result",
[
(b"localhost?code=abc123", "abc123"),
(b"localhost?error=bad_login", LocalServerError("bad_login")),
(b"localhost", LocalServerError(None)),
(b"localhost?error=bad_login", LocalServerLoginError("bad_login")),
(b"localhost", LocalServerLoginError(None)),
],
)
def test_server(url, expected_result):
@@ -54,7 +54,7 @@ def test_server(url, expected_result):
result = server.wait_for_code()
if isinstance(result, str):
assert result == expected_result
elif isinstance(result, LocalServerError):
elif isinstance(result, LocalServerLoginError):
assert result.args == expected_result.args
else:
raise AssertionError("unexpected result type")

0 comments on commit f1f450c

Please sign in to comment.