Flows API
  • Globus Flows
  • Overview
  • Getting Started
    • How to Run a Flow
    • How to Monitor a Flow Run
    • How to Create a Flow
  • Authoring Flows
    • Introduction
    • Actions
    • Expressions
    • Choice States
    • Wait States
    • Fail States
    • Pass States
    • Protecting Secrets
    • Handling Exceptions
    • Performing Actions as Different Users
    • Run Context
    • Validating Flow Definitions
  • Authoring Input Schemas
  • Authentication and Authorization
  • Consents and Resuming Runs
  • Permissions
  • Limits
  • Hosted Action Providers
    • Hello World
    • Globus Search - Ingest Task
    • Globus Search - Delete Task
    • Send Notification Email
    • Wait For User Selection
    • Expression Evaluation
    • DataCite Mint
    • Transfer APs
    • Compute AP
  • Example Flows
    • Simple Transfer
    • Move (copy and delete) files
    • Transfer and Share Files
    • Two Stage Globus Transfer
    • Transfer After Approval
    • Looping Batched Move
    • Tar and Transfer with Globus Compute
Skip to main content
Globus Docs
  • APIs
    Auth Flows Groups Search Timers Transfer Globus Connect Server Compute Helper Pages
  • Applications
    Globus Connect Personal Globus Connect Server Premium Storage Connectors Compute Command Line Interface Python SDK JavaScript SDK
  • Guides
  • Support
    FAQs Mailing Lists Contact Us Check Support Tickets
  1. Home
  2. Globus Services
  3. Globus Flows
  4. Example Flows
  5. Transfer After Approval

Transfer After Approval

Description

Allow users to submit an approval request to transfer a file to a destination Guest Collection.

The request will come in the form of emails to curators who are authorized to approve the transfer. If approved, the file will be transferred using the flow's own identity — not the user’s — ensuring that users do not need permission to read or write to the destination collection.

Users who run the flow will not be informed who the curators/approvers are, nor will they be informed by the flow itself where the file is transferred to. They only need to know that "this flow allows you to submit a file for transfer."

Example uses include:

  • Allowing researchers to submit research data and findings for consideration and publication.

  • Accepting vulnerability analyses from unknown sources.

Highlights

This flow has several technical highlights.

  • __Private_Parameters and _private parameter prefixes are used throughout. This helps restrict visibility of private information (such as email addresses and SMTP credentials) when users view the flow definition as well as while their runs of the flow are executing.

    For more information, see Protecting Secrets in the Authoring Flows documentation.

  • By setting the RunAs value in the TransferFile state, the transfer operation will be performed using the flow's own identity to access the destination collection. This allows the flow to be made public — and run by any user — without having to give individual users write permission on the destination collection.

Prerequisites

The flow definition must be modified before using it to create a new flow. It also has several requirements when running.

Modification requirements

  • The SetupEmailLoop state must be modified. The email addresses of approvers must be updated, and unique IDs must be assigned to each reviewer. Finally, the loop_end value must be updated to match the number of approvers.

  • The SendEmail state must be modified. Specifically, the SMTP hostname, username, and password in send_credentials must be updated, as well as the email address in sender.

    It is also possible to use an AWS SES credential for sending emails. See the Notification action provider documentation for more details.

  • The TransferFile state must be modified. The destination_endpoint must be set to the destination Guest Collection ID that will receive approved files, and the destination_path should match the target directory you want approved files transferred to.

Execution requirements

  • After modifying the flow definition and creating the flow in the Globus Flows service, a destination collection administrator must give the flow client write permission on the collection.

    This can be accomplished in the Web App, in the destination collection's Permissions tab, by clicking the "Add Permissions" button and searching for the flow ID as the username to share with, and ensuring that the "Write" checkbox is checked.

    It’s also possible to use the Globus CLI to accomplish this, using the globus endpoint role create command:

    globus endpoint permission create $DESTINATION_COLLECTION_ID:/ --permissions rw --identity $FLOW_ID
  • Users who seek approval to transfer a file must select a file on a Guest Collection on which they can grant new permissions.

These execution requirements allow the flow — acting as the user — to give itself read permission on the user’s Guest Collection, and then — acting as the flow itself — to transfer the user’s file to the destination collection.

Source code

{
  "Comment": "Request approval to transfer a file from the requester's Guest Collection into a destination collection. If approved, a transfer will be performed using the flow's identity (meaning the flow itself will need write access on the destination collection).",
  "StartAt": "GetSourceCollectionInfo",
  "States": {
    "GetSourceCollectionInfo": {
      "Type": "Action",
      "Next": "ValidateSourceCollectionType",
      "ActionUrl": "https://transfer.actions.globus.org/collection_info",
      "ResultPath": "$.collection_info",
      "Parameters": {
        "endpoint_id.$": "$.source.id"
      }
    },
    "ValidateSourceCollectionType": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.collection_info.details.entity_type",
          "StringEquals": "GCSv5_guest_collection",
          "Next": "GetSourcePathInfo"
        }
      ],
      "Default": "FailSourceCollectionType"
    },
    "GetSourcePathInfo": {
      "Type": "Action",
      "Next": "ValidateSourcePathType",
      "ActionUrl": "https://transfer.actions.globus.org/stat",
      "Parameters": {
        "endpoint_id.$": "$.source.id",
        "path.$": "$.source.path"
      },
      "ResultPath": "$.source_stat"
    },
    "ValidateSourcePathType": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.source_stat.details.type",
          "StringEquals": "file",
          "Next": "SetupEmailLoop"
        }
      ],
      "Default": "FailSourcePathType"
    },
    "SetupEmailLoop": {
      "Comment": "Each random ID for each `reviewer` should be unique and unguessable, and `loop_end` should match the number of `reviewers` defined here.",
      "Type": "Pass",
      "Next": "SendEmail",
      "Parameters": {
        "__Private_Parameters": [
          "reviewers",
          "loop_index",
          "loop_end"
        ],
        "reviewers": [
          {
            "email": "reviewer1@domain.example",
            "random_id": "e8c63401"
          },
          {
            "email": "reviewer2@domain.example",
            "random_id": "7fc71f01"
          }
        ],
        "loop_index": 0,
        "loop_end": 2
      },
      "ResultPath": "$._private_state"
    },
    "SendEmail": {
      "Type": "Action",
      "Next": "IncrementLoopIndex",
      "ActionUrl": "https://actions.globus.org/notification/notify",
      "ResultPath": "$._private_email_result",
      "Parameters": {
        "__Private_Parameters": [
          "body_mimetype",
          "body_template",
          "body_variables",
          "destination",
          "send_credentials",
          "sender",
          "subject"
        ],
        "body_mimetype": "text/html",
        "body_template": "<p>The following file has been submitted for review. It must be approved before it can be transferred.</p><p><table><tr><td>Submitter</td><td><code>${SUBMITTER}</code></td></tr><tr><td>Filename</td><td><code>${FILE_NAME}</code></td></tr><tr><td>Size (bytes)</td><td><code>${FILE_SIZE}</code></td></tr></table></p><p><ul><li><a href=\"${APPROVAL_URL}\">\u2705 Approve transfer</a></li><li><a href=\"${REJECTION_URL}\">\u274c Reject transfer</a></li></ul></p>",
        "body_variables": {
          "FILE_NAME.$": "$.source_stat.details.name",
          "FILE_SIZE.$": "$.source_stat.details.size",
          "SUBMITTER.=": "(_context.username + ' &lt;' + _context.email + '&gt;') if _context.email and (_context.username != _context.email) else _context.username",
          "APPROVAL_URL.=": "'https://actions.globus.org/weboption/option/' + _context.run_id + '-' + _private_state.reviewers[_private_state.loop_index].random_id + '-approve'",
          "REJECTION_URL.=": "'https://actions.globus.org/weboption/option/' + _context.run_id + '-' + _private_state.reviewers[_private_state.loop_index].random_id + '-reject'"
        },
        "destination.=": "_private_state.reviewers[_private_state.loop_index].email",
        "send_credentials": [
          {
            "credential_type": "smtp",
            "credential_value": {
              "hostname": "smtp.domain.example",
              "username": "email@domain.example",
              "password": "email-password",
              "port": 587
            }
          }
        ],
        "sender": "flows@domain.example",
        "subject": "Approval needed for file transfer"
      }
    },
    "IncrementLoopIndex": {
      "Type": "ExpressionEval",
      "Next": "ExitEmailLoop",
      "Parameters": {
        "__Private_Parameters": [
          "reviewers",
          "loop_index",
          "loop_end"
        ],
        "reviewers.$": "$._private_state.reviewers",
        "loop_index.=": "_private_state.loop_index + 1",
        "loop_end.$": "$._private_state.loop_end"
      },
      "ResultPath": "$._private_state"
    },
    "ExitEmailLoop": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$._private_state.loop_index",
          "NumericLessThanPath": "$._private_state.loop_end",
          "Next": "SendEmail"
        }
      ],
      "Default": "AddOptions"
    },
    "AddOptions": {
      "Type": "ExpressionEval",
      "Parameters": {
        "__Private_Parameters": [
          "options"
        ],
        "options.=": "[{'name': word, 'url_suffix': _context.run_id + '-' + reviewer.random_id + '-' + word} for reviewer in _private_state.reviewers for word in ['approve', 'reject']]"
      },
      "ResultPath": "$._private_state",
      "Next": "WaitForApproval"
    },
    "WaitForApproval": {
      "Type": "Action",
      "Next": "DetermineOutcome",
      "ActionUrl": "https://actions.globus.org/weboption/wait_for_option",
      "Parameters": {
        "__Private_Parameters": [
          "options"
        ],
        "options.$": "$._private_state.options"
      },
      "ResultPath": "$._private_judgement"
    },
    "DetermineOutcome": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$._private_judgement.details.name",
          "StringEquals": "approve",
          "Next": "GetExistingPathPermissions"
        }
      ],
      "Default": "SubmissionRejected"
    },
    "GetExistingPathPermissions": {
      "Type": "Action",
      "Next": "CalculateAvailablePermissions",
      "ActionUrl": "https://transfer.actions.globus.org/manage_permission",
      "ResultPath": "$.permission_list",
      "Parameters": {
        "operation": "LIST",
        "endpoint_id.$": "$.source.id"
      }
    },
    "CalculateAvailablePermissions": {
      "Type": "ExpressionEval",
      "Next": "DetermineWhetherToCreatePermission",
      "ResultPath": "$.permission_judgement",
      "Parameters": {
        "access_id.=": "([permission['id'] for permission in permission_list.details.DATA if permission['principal'] == _context.flow_id and permission['principal_type'] == 'identity' and permission['path'] == source.path.rsplit('/', 1)[0] + '/'] or [''])[0]"
      }
    },
    "DetermineWhetherToCreatePermission": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.permission_judgement.access_id",
          "StringEquals": "",
          "Next": "CreatePermission"
        }
      ],
      "Default": "TransferFile"
    },
    "CreatePermission": {
      "Type": "Action",
      "Next": "TransferFile",
      "ActionUrl": "https://transfer.actions.globus.org/manage_permission",
      "ResultPath": "$.permission_creation",
      "Parameters": {
        "operation": "CREATE",
        "endpoint_id.$": "$.source.id",
        "path.=": "source.path.rsplit('/')[0] + '/'",
        "principal.$": "$._context.flow_id",
        "principal_type": "identity",
        "permissions": "r"
      }
    },
    "TransferFile": {
      "Comment": "The destination_path injects the requesting user's ID into the filename to help prevent other files from getting erased.",
      "Type": "Action",
      "RunAs": "Flow",
      "ResultPath": "$._private_transfer_result",
      "Next": "DeletePermission",
      "ActionUrl": "https://transfer.actions.globus.org/transfer",
      "Parameters": {
        "__Private_Parameters": [
          "source_endpoint",
          "destination_endpoint",
          "DATA"
        ],
        "source_endpoint.$": "$.source.id",
        "destination_endpoint": "00000000-bacb-424d-bbbe-be786aacd771",
        "DATA": [
          {
            "source_path.$": "$.source.path",
            "destination_path.=": "'/Inbox/' + _context.user_id.split(':')[-1] + '-' + source_stat.details.name"
          }
        ]
      }
    },
    "DeletePermission": {
      "Type": "Action",
      "Next": "SubmissionAccepted",
      "ActionUrl": "https://transfer.actions.globus.org/manage_permission",
      "ResultPath": "$.permission_deletion",
      "Parameters": {
        "operation": "DELETE",
        "endpoint_id.$": "$.source.id",
        "rule_id.=": "permission_judgement.access_id or permission_creation.details.access_id"
      }
    },
    "FailSourceCollectionType": {
      "Type": "Fail",
      "Error": "IncorrectSourceCollectionType",
      "Cause": "The source collection must be a Guest Collection"
    },
    "FailSourcePathType": {
      "Type": "Fail",
      "Error": "IncorrectSourcePathType",
      "Cause": "Only files may be selected when using this flow."
    },
    "SubmissionRejected": {
      "Type": "Pass",
      "End": true
    },
    "SubmissionAccepted": {
      "Type": "Pass",
      "End": true
    }
  }
}
{
  "required": [
    "source"
  ],
  "additionalProperties": false,
  "properties": {
    "source": {
      "title": "File to submit for approval",
      "description": "The file selected here will be reviewed prior to transfer. Note that the source collection must be a Guest Collection that you have the ability to add permissions to.",
      "type": "object",
      "format": "globus-collection",
      "required": [
        "id",
        "path"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "path": {
          "type": "string"
        }
      }
    }
  }
}
{
  "source": {
    "id": "00000000-f206-43d2-b5bb-2c9dc579337d",
    "path": "/publication-submission.pdf"
  }
}
  • Globus Flows
  • Overview
  • Getting Started
    • How to Run a Flow
    • How to Monitor a Flow Run
    • How to Create a Flow
  • Authoring Flows
    • Introduction
    • Actions
    • Expressions
    • Choice States
    • Wait States
    • Fail States
    • Pass States
    • Protecting Secrets
    • Handling Exceptions
    • Performing Actions as Different Users
    • Run Context
    • Validating Flow Definitions
  • Authoring Input Schemas
  • Authentication and Authorization
  • Consents and Resuming Runs
  • Permissions
  • Limits
  • Hosted Action Providers
    • Hello World
    • Globus Search - Ingest Task
    • Globus Search - Delete Task
    • Send Notification Email
    • Wait For User Selection
    • Expression Evaluation
    • DataCite Mint
    • Transfer APs
    • Compute AP
  • Example Flows
    • Simple Transfer
    • Move (copy and delete) files
    • Transfer and Share Files
    • Two Stage Globus Transfer
    • Transfer After Approval
    • Looping Batched Move
    • Tar and Transfer with Globus Compute
© 2010- The University of Chicago Legal Privacy Accessibility