1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
|
.. _using_globus_app:
.. py:currentmodule:: globus_sdk
Using a GlobusApp
=================
Programmatic communication with Globus services relies on the authorization of requests.
Management and resolution of this authorization can become an arduous task, especially
when a script needs to interact with different services each carrying an individual set
of complex authentication and authorization requirements. To assist with this task, this
library provides a utility construct called a GlobusApp.
A :py:class:`~GlobusApp` is a distinct object which will manage Globus Auth requirements
(i.e., **scopes**) identified by their associated service (i.e., **resource server**).
In addition to storing them, a GlobusApp provides a mechanism to resolve unmet
requirements through browser- and API-based authorization flows, supplying the resulting
tokens to bound clients as requested.
There are two flavors of GlobusApp:
* :py:class:`~UserApp`, a GlobusApp for interactions between an end user and Globus
services. Operations are performed as a *user identity*.
* :py:class:`~ClientApp`, a GlobusApp for interactions between a client
(i.e. service account) and Globus services. Operations are performed as a
*client identity*.
Setup
-----
A GlobusApp is a heavily configurable object. For common scripting usage however,
instantiation only requires two parameters:
#. **App Name** - A human readable name to identify your app in HTTP requests and token
caching (e.g., "My Cool Weathervane").
#. **Client Info** - either a *Native Client's* ID or a *Confidential Client's* ID and
secret pair.
* There are important distinctions to consider when choosing your client type; see
`Developing an Application Using Globus Auth <https://docs.globus.org/api/auth/developer-guide/#developing-apps>`_.
A simplified heuristic to help choose the client type however is:
* Use a *Confidential Client* when your client needs to own cloud resources
itself and will be used in a trusted environment where you can securely
hold a secret.
* Use a *Native Client* when your client will be facilitating interactions
between a user and a service, particularly if it is bundled within a
script or cli tool to be distributed to end-users' machines.
.. Note::
Both UserApps and ClientApps require a client.
In a UserApp, the client sends requests representing a user identity; whereas in
a ClientApp, the requests represent the client identity itself.
Once instantiated, a GlobusApp can be passed to any service client using the init
``app`` keyword argument (e.g. ``TransferClient(app=my_app)``). Doing this will bind the
app to the client, registering a default set of scopes requirements for the service
client's resource server and configuring the app as the service client's auth provider.
.. tab-set::
.. tab-item:: UserApp
Construct a UserApp then bind it to a Transfer client and a Flows client.
.. Note::
``UserApp.__init__(...)`` currently only supports Native clients.
Confidential client support is forthcoming.
.. code-block:: python
import globus_sdk
CLIENT_ID = "..."
my_app = globus_sdk.UserApp("my-user-app", client_id=CLIENT_ID)
transfer_client = globus_sdk.TransferClient(app=my_app)
flows_client = globus_sdk.FlowsClient(app=my_app)
.. tab-item:: ClientApp
Construct a ClientApp, then bind it to a Transfer client and a Flows client.
.. Note::
``ClientApp.__init__(...)`` requires the ``client_secret`` keyword argument.
Native clients, which lack secrets, are not allowed.
.. code-block:: python
import globus_sdk
CLIENT_ID = "..."
CLIENT_SECRET = "..."
my_app = globus_sdk.ClientApp(
"my-client-app", client_id=CLIENT_ID, client_secret=CLIENT_SECRET
)
transfer_client = globus_sdk.TransferClient(app=my_app)
flows_client = globus_sdk.FlowsClient(app=my_app)
Usage
-----
From this point, the app manages scope validation, token caching and routing for any
bound clients.
In the above example, listing a client's or user's flows becomes as simple as:
.. code-block:: python
flows = flows_client.list_flows()["flows"]
If cached tokens are missing, expired, or otherwise insufficient (e.g., the first time
you run the script), the app will automatically initiate an auth flow to acquire new
tokens. With a UserApp, the app will print a URL to the terminal with a prompt
instructing a the user to follow the link and enter the code they're given back into the
terminal. With a ClientApp, the app will retrieve tokens programmatically through a
Globus Auth API.
Once this auth flow has finished, the app will cache tokens for future use and
invocation of your requested method will proceed as expected.
Manually Running Login Flows
----------------------------
While your app will automatically initiate and oversee login flows when needed,
sometimes an author may want to explicitly control when an authorization occurs. To
manually trigger a login flow, call ``GlobusApp.login(...)``. The app will evaluate the
current scope requirements against available tokens, initiating a login flow if it
determines that any requirements across any resource servers are unmet. Resulting tokens
will be cached for future use.
This method accepts two optional keyword args:
- ``auth_params``, a collection of additional auth parameters to customize the login.
This allows for specifications such as requiring that a user be logged in with an
MFA token or rendering the authorization webpage with a specific message.
- ``force``, a boolean flag instructing the app to perform a login flow regardless of
whether it is required.
.. code-block:: python
from globus_sdk.gare import GlobusAuthorizationParameters
...
my_app.login(
auth_params=GlobusAuthorizationParameters(
session_message="Please authenticate with MFA",
session_required_mfa=True,
)
)
Manually Defining Scope Requirements
------------------------------------
Globus service client classes all maintain an internal list of default scope
requirements to be attached to any bound app. These scopes represent an approximation of
a "standard set" for each service. This list however is not sufficient for all use
cases.
For example, the FlowsClient defines its default scopes as ``flows:view_flows`` and
``flows:run_status`` (read-only access). These scopes will not be sufficient for a
script which needs to create new flows or modify existing ones. For that script, the
author must manually attach the ``flows:manage_flows`` scope to the app.
This can be done in one of two ways:
#. Through a service client initialization, using the ``app_scopes`` kwarg.
.. code-block:: python
from globus_sdk import Scope, FlowsClient
FlowsClient(app=my_app, app_scopes=[Scope(FlowsClient.scopes.manage_flows)])
This approach results in an app which only requires the ``flows:manage_flows``
scope. The default scopes (``flows:view_flows`` and ``flows:run_status``) are not
registered.
#. Through a service client's ``add_app_scope`` method.
.. code-block:: python
from globus_sdk import FlowsClient
flows_client = FlowsClient(app=my_app)
flows_client.add_app_scope(FlowsClient.scopes.manage_flows)
This approach will add the ``flows:manage_flows`` scope to the app's existing set of
scopes. Since ``app_scopes`` was omitted in the client initialization, the default
scopes are registered as well.
|