middleware.go raw

   1  package presignedurl
   2  
   3  import (
   4  	"context"
   5  	"fmt"
   6  
   7  	awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
   8  	v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
   9  
  10  	"github.com/aws/smithy-go/middleware"
  11  )
  12  
  13  // URLPresigner provides the interface to presign the input parameters in to a
  14  // presigned URL.
  15  type URLPresigner interface {
  16  	// PresignURL presigns a URL.
  17  	PresignURL(ctx context.Context, srcRegion string, params interface{}) (*v4.PresignedHTTPRequest, error)
  18  }
  19  
  20  // ParameterAccessor provides an collection of accessor to for retrieving and
  21  // setting the values needed to PresignedURL generation
  22  type ParameterAccessor struct {
  23  	// GetPresignedURL accessor points to a function that retrieves a presigned url if present
  24  	GetPresignedURL func(interface{}) (string, bool, error)
  25  
  26  	// GetSourceRegion accessor points to a function that retrieves source region for presigned url
  27  	GetSourceRegion func(interface{}) (string, bool, error)
  28  
  29  	// CopyInput accessor points to a function that takes in an input, and returns a copy.
  30  	CopyInput func(interface{}) (interface{}, error)
  31  
  32  	// SetDestinationRegion accessor points to a function that sets destination region on api input struct
  33  	SetDestinationRegion func(interface{}, string) error
  34  
  35  	// SetPresignedURL accessor points to a function that sets presigned url on api input struct
  36  	SetPresignedURL func(interface{}, string) error
  37  }
  38  
  39  // Options provides the set of options needed by the presigned URL middleware.
  40  type Options struct {
  41  	// Accessor are the parameter accessors used by this middleware
  42  	Accessor ParameterAccessor
  43  
  44  	// Presigner is the URLPresigner used by the middleware
  45  	Presigner URLPresigner
  46  }
  47  
  48  // AddMiddleware adds the Presign URL middleware to the middleware stack.
  49  func AddMiddleware(stack *middleware.Stack, opts Options) error {
  50  	return stack.Initialize.Add(&presign{options: opts}, middleware.Before)
  51  }
  52  
  53  // RemoveMiddleware removes the Presign URL middleware from the stack.
  54  func RemoveMiddleware(stack *middleware.Stack) error {
  55  	_, err := stack.Initialize.Remove((*presign)(nil).ID())
  56  	return err
  57  }
  58  
  59  type presign struct {
  60  	options Options
  61  }
  62  
  63  func (m *presign) ID() string { return "Presign" }
  64  
  65  func (m *presign) HandleInitialize(
  66  	ctx context.Context, input middleware.InitializeInput, next middleware.InitializeHandler,
  67  ) (
  68  	out middleware.InitializeOutput, metadata middleware.Metadata, err error,
  69  ) {
  70  	// If PresignedURL is already set ignore middleware.
  71  	if _, ok, err := m.options.Accessor.GetPresignedURL(input.Parameters); err != nil {
  72  		return out, metadata, fmt.Errorf("presign middleware failed, %w", err)
  73  	} else if ok {
  74  		return next.HandleInitialize(ctx, input)
  75  	}
  76  
  77  	// If have source region is not set ignore middleware.
  78  	srcRegion, ok, err := m.options.Accessor.GetSourceRegion(input.Parameters)
  79  	if err != nil {
  80  		return out, metadata, fmt.Errorf("presign middleware failed, %w", err)
  81  	} else if !ok || len(srcRegion) == 0 {
  82  		return next.HandleInitialize(ctx, input)
  83  	}
  84  
  85  	// Create a copy of the original input so the destination region value can
  86  	// be added. This ensures that value does not leak into the original
  87  	// request parameters.
  88  	paramCpy, err := m.options.Accessor.CopyInput(input.Parameters)
  89  	if err != nil {
  90  		return out, metadata, fmt.Errorf("unable to create presigned URL, %w", err)
  91  	}
  92  
  93  	// Destination region is the API client's configured region.
  94  	dstRegion := awsmiddleware.GetRegion(ctx)
  95  	if err = m.options.Accessor.SetDestinationRegion(paramCpy, dstRegion); err != nil {
  96  		return out, metadata, fmt.Errorf("presign middleware failed, %w", err)
  97  	}
  98  
  99  	presignedReq, err := m.options.Presigner.PresignURL(ctx, srcRegion, paramCpy)
 100  	if err != nil {
 101  		return out, metadata, fmt.Errorf("unable to create presigned URL, %w", err)
 102  	}
 103  
 104  	// Update the original input with the presigned URL value.
 105  	if err = m.options.Accessor.SetPresignedURL(input.Parameters, presignedReq.URL); err != nil {
 106  		return out, metadata, fmt.Errorf("presign middleware failed, %w", err)
 107  	}
 108  
 109  	return next.HandleInitialize(ctx, input)
 110  }
 111