Handling Authorization Parameters from a Transfer Operation

Globus Transfer interacts with Collections and their underlying Endpoints both synchronously and asynchronously.

The example below demonstrates a synchronous interaction – ls on a collection – with handling for the possibility that a session-related error is returned. The session error can be parsed into a Globus Auth Requirements Error document – a globus_sdk.gare.GARE – via globus_sdk.gare. Once the error is recognized, the parameters parsed from it can be fed back into a login flow. Doing so will update the user’s session, resolving the issue.

Creating a Test Collection

In order to test this sample script in full, you will need a collection with a session policy which requires users to login with a specific ID or within a certain time period. The following section covers a recommended test collection configuration. This will require administrative access to a Globus Connect Server endpoint.

Note

Globus tutorial resources do not contain session policies, making them easier to use. However, this means they are not suitable for the example script given below. Tutorial collections will work, but they will not demonstrate the interesting features in use.

Create a POSIX Storage Gateway With a Timeout Policy

On a Globus Connect Server Endpoint which you administer, create a new Storage Gateway with a policy explicitly designed for testing. The gateway will need the following properties, with the following recommended values:

  • gateway type: posix

  • display name: sdk-test-policy

  • root path: /

  • domain requirements: require a domain in which you have an identity

  • authentication timeout in minutes: 1

You can create a gateway with a command like the following, which uses the uchicago.edu domain as an example:

globus-connect-server storage-gateway create posix \
    sdk-test-gateway --authentication-timeout-mins 1 \
    --domain uchicago.edu

Record the storage gateway ID:

STORAGE_GATEWAY_ID="..."

Using a one minute timeout will make the session handling very easy to test. Simply interact with the collection, wait one minute, and then try using it again – you should be prompted to log in a second time to meet the timeout requirement.

Tip

You do not need to be an administrator for the domain you set, but you do need an identity from that domain. You are setting a policy for your Storage Gateway with this setting to require identities from that domain.

If you don’t have a uchicago.edu identity, you could create a gateway like the example, but you wouldn’t be able to use it!

Create a Mapped Collection on that Gateway

The Storage Gateway encodes the requisite policy. No special configuration is needed for the mapped collection used for testing. Simply create a collection on the test gateway, e.g.:

globus-connect-server collection create \
    "$STORAGE_GATEWAY_ID" / sdk-test-collection

Note the collection ID, we will need it in the following steps.

Run ls on the Collection

Running a simple ls on this collection is no different from doing so on any other collection. We have setup a 1 minute timeout for the test collection, so this will likely work the first time that it runs.

import globus_sdk

# this is the SDK tutorial client ID, replace with your own ID
NATIVE_CLIENT_ID = "61338d24-54d5-408f-a10d-66c06b59f6d2"
# set the collection ID to your test collection
COLLECTION_ID = "..."
USER_APP = globus_sdk.UserApp("ls-session", client_id=NATIVE_CLIENT_ID)


client = globus_sdk.TransferClient(app=USER_APP)

# because the recommended test configuration uses a mapped collection
# without High Assurance capabilities, it will have a data_access scope
# requirement
# comment out this line if your collection does not use data_access
client.add_app_data_access_scope(COLLECTION_ID)

ls_result = client.operation_ls(COLLECTION_ID)
for item in ls_result:
    name = item["name"]
    if item["type"] == "dir":
        name += "/"
    print(name)

Run Again, Observe the Session Error

The 1 minute timeout guarantees that we can trivially produce a session timeout error by waiting one minute and running the script again. You should see a stacktrace like the following:

Traceback (most recent call last):
  File "/home/demo-user/ls_session.py", line 18, in <module>
    ls_result = client.operation_ls(COLLECTION_ID)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/demo-user/.venv/lib/python3.2/globus_sdk/services/transfer/client.py", line 1286, in operation_ls
    self.get(f"operation/endpoint/{endpoint_id}/ls", query_params=query_params)
  File "/home/demo-user/.venv/lib/python3.2/globus_sdk/client.py", line 273, in get
    return self.request(
           ^^^^^^^^^^^^^
  File "/home/demo-user/.venv/lib/python3.2/globus_sdk/client.py", line 461, in request
    raise self.error_class(r)
globus_sdk.services.transfer.errors.TransferAPIError: ('GET', 'https://transfer.api.globus.org/v0.10/operation/endpoint/2a0d28b8-d2a3-4143-9f76-93ab09497b85/ls', 'Bearer', 502, 'ExternalError.DirListingFailed.LoginFailed', 'Command Failed: Error (login)\nEndpoint: uc-restricted-col (2a0d28b8-d2a3-4143-9f76-93ab09497b85)\nServer: 44.201.14.78:443\nMessage: Login Failed\n---\nDetails: 530-Login incorrect. : GlobusError: v=1 c=LOGIN_DENIED\\r\\n530-GridFTP-Message: None of your authenticated identities are from domains allowed by resource policies\\r\\n530-GridFTP-JSON-Result: {"DATA_TYPE": "result#1.1.0", "authorization_parameters": {"session_required_single_domain": ["uchicago.edu"]}, "code": "permission_denied", "detail": {"DATA_TYPE": "not_from_allowed_domain#1.0.0", "allowed_domains": ["uchicago.edu"]}, "has_next_page": false, "http_response_code": 403, "message": "None of your authenticated identities are from domains allowed by resource policies"}\\r\\n530 End.\\r\\n\n', '7U5dNqzDn')

The exact details will vary, but the primary content of the error is that you do not satisfy the session requirements of the collection.

Parse the Error and Confirm authorization_parameters are present

Handling and parsing this error requires that we add an exception handler. To enhance readability, we will wrap the ls operation in a function.

The caught exception will have an info property with populated authorization_parameters. We can check for this easily:

import globus_sdk

# this is the SDK tutorial client ID, replace with your own ID
NATIVE_CLIENT_ID = "61338d24-54d5-408f-a10d-66c06b59f6d2"
# set the collection ID to your test collection
COLLECTION_ID = "..."
USER_APP = globus_sdk.UserApp("ls-session", client_id=NATIVE_CLIENT_ID)

client = globus_sdk.TransferClient(app=USER_APP)

# because the recommended test configuration uses a mapped collection
# without High Assurance capabilities, it will have a data_access scope
# requirement
# comment out this line if your collection does not use data_access
client.add_app_data_access_scope(COLLECTION_ID)


def print_ls_data():
    ls_result = client.operation_ls(COLLECTION_ID)
    for item in ls_result:
        name = item["name"]
        if item["type"] == "dir":
            name += "/"
        print(name)


# do the `ls` and detect the authorization_parameters as being present
try:
    print_ls_data()
except globus_sdk.TransferAPIError as err:
    if err.info.authorization_parameters:
        print("An authorization requirement was not met.")
    else:
        raise

Convert the Error to GARE Format

Globus Auth Requirements Errors (“GAREs”) are a standardized format for Globus Auth session information to be passed between components. When these authorization_parameters errors are encountered, they can be converted to the GARE format using the tooling in globus_sdk.gare:

import globus_sdk.gare

try:
    print_ls_data()
except globus_sdk.TransferAPIError as err:
    if err.info.authorization_parameters:
        print("An authorization requirement was not met.")
        gare = globus_sdk.gare.to_gare(err)
        print("GARE data:", str(gare))
    else:
        raise

Redrive Logins with the Parsed Authorization Params

Now that we have the data parsed into GARE format, it is relatively simple to pass the parsed authorization_parameters data to a login flow. Because the underlying requirement in this case is a new login flow, we must also set prompt=login in the parameters.

Having done so, we can repeat the login step using that information:

try:
    print_ls_data()
except globus_sdk.TransferAPIError as err:
    if err.info.authorization_parameters:
        print("An authorization requirement was not met. Logging in again...")

        gare = globus_sdk.gare.to_gare(err)
        params = gare.authorization_parameters
        # set 'prompt=login', which guarantees a fresh login without
        # reliance on the browser session
        params.prompt = "login"

        # pass these parameters into a login flow
        USER_APP.login(auth_params=params)
    else:
        raise

Summary: Complete Example

Combining the pieces above, we can construct a working complete example script. A collection with session requirements is needed in order to demonstrate the behavior.

ls_with_session_handling.py [download]
import globus_sdk
import globus_sdk.gare

# this is the SDK tutorial client ID, replace with your own ID
NATIVE_CLIENT_ID = "61338d24-54d5-408f-a10d-66c06b59f6d2"
# set the collection ID to your test collection
COLLECTION_ID = "..."
USER_APP = globus_sdk.UserApp("ls-session", client_id=NATIVE_CLIENT_ID)


client = globus_sdk.TransferClient(app=USER_APP)

# because the recommended test configuration uses a mapped collection
# without High Assurance capabilities, it will have a data_access scope
# requirement
# comment out this line if your collection does not use data_access
client.add_app_data_access_scope(COLLECTION_ID)


def print_ls_data():
    ls_result = client.operation_ls(COLLECTION_ID)
    for item in ls_result:
        name = item["name"]
        if item["type"] == "dir":
            name += "/"
        print(name)


# try to run the desired operation (`print_ls_data`)
try:
    print_ls_data()
# catch the possible API error
except globus_sdk.TransferAPIError as err:
    # if there are authorization parameters data in the error,
    # use it to redrive login
    if err.info.authorization_parameters:
        print("An authorization requirement was not met. Logging in again...")

        gare = globus_sdk.gare.to_gare(err)
        params = gare.authorization_parameters
        # set 'prompt=login', which guarantees a fresh login without
        # reliance on the browser session
        params.prompt = "login"

        # pass these parameters into a login flow
        USER_APP.login(auth_params=params)

        # rerun the desired print_ls_data() operation
        print_ls_data()

    # otherwise, there are no authorization parameters, so reraise the error
    else:
        raise