Bump github.com/tus/tusd from 1.10.1 to 1.12.1

Bumps [github.com/tus/tusd](https://github.com/tus/tusd) from 1.10.1 to 1.12.1.
- [Release notes](https://github.com/tus/tusd/releases)
- [Commits](https://github.com/tus/tusd/compare/v1.10.1...v1.12.1)

---
updated-dependencies:
- dependency-name: github.com/tus/tusd
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-07-26 06:20:19 +00:00
committed by Ralf Haferkamp
parent cbe945af4a
commit 97d3aec987
29 changed files with 5744 additions and 1214 deletions
+5 -5
View File
@@ -76,7 +76,7 @@ require (
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/thejerf/suture/v4 v4.0.2 github.com/thejerf/suture/v4 v4.0.2
github.com/tus/tusd v1.10.1 github.com/tus/tusd v1.12.1
github.com/urfave/cli/v2 v2.25.7 github.com/urfave/cli/v2 v2.25.7
github.com/xhit/go-simple-mail/v2 v2.15.0 github.com/xhit/go-simple-mail/v2 v2.15.0
go-micro.dev/v4 v4.9.0 go-micro.dev/v4 v4.9.0
@@ -93,7 +93,7 @@ require (
golang.org/x/image v0.9.0 golang.org/x/image v0.9.0
golang.org/x/net v0.12.0 golang.org/x/net v0.12.0
golang.org/x/oauth2 v0.10.0 golang.org/x/oauth2 v0.10.0
golang.org/x/sync v0.2.0 golang.org/x/sync v0.3.0
golang.org/x/term v0.10.0 golang.org/x/term v0.10.0
golang.org/x/text v0.11.0 golang.org/x/text v0.11.0
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc
@@ -124,7 +124,7 @@ require (
github.com/armon/go-metrics v0.4.1 // indirect github.com/armon/go-metrics v0.4.1 // indirect
github.com/armon/go-radix v1.0.0 // indirect github.com/armon/go-radix v1.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/aws/aws-sdk-go v1.44.181 // indirect github.com/aws/aws-sdk-go v1.44.294 // indirect
github.com/beevik/etree v1.2.0 // indirect github.com/beevik/etree v1.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bitly/go-simplejson v0.5.0 // indirect github.com/bitly/go-simplejson v0.5.0 // indirect
@@ -217,7 +217,7 @@ require (
github.com/gookit/goutil v0.6.9 // indirect github.com/gookit/goutil v0.6.9 // indirect
github.com/gorilla/handlers v1.5.1 // indirect github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/schema v1.2.0 // indirect github.com/gorilla/schema v1.2.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/hashicorp/consul/api v1.15.2 // indirect github.com/hashicorp/consul/api v1.15.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
@@ -327,7 +327,7 @@ require (
golang.org/x/tools v0.9.3 // indirect golang.org/x/tools v0.9.3 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
+476 -12
View File
File diff suppressed because it is too large Load Diff
+46 -18
View File
@@ -20,16 +20,16 @@ type RequestRetryer interface{}
// A Config provides service configuration for service clients. By default, // A Config provides service configuration for service clients. By default,
// all clients will use the defaults.DefaultConfig structure. // all clients will use the defaults.DefaultConfig structure.
// //
// // Create Session with MaxRetries configuration to be shared by multiple // // Create Session with MaxRetries configuration to be shared by multiple
// // service clients. // // service clients.
// sess := session.Must(session.NewSession(&aws.Config{ // sess := session.Must(session.NewSession(&aws.Config{
// MaxRetries: aws.Int(3), // MaxRetries: aws.Int(3),
// })) // }))
// //
// // Create S3 service client with a specific Region. // // Create S3 service client with a specific Region.
// svc := s3.New(sess, &aws.Config{ // svc := s3.New(sess, &aws.Config{
// Region: aws.String("us-west-2"), // Region: aws.String("us-west-2"),
// }) // })
type Config struct { type Config struct {
// Enables verbose error printing of all credential chain errors. // Enables verbose error printing of all credential chain errors.
// Should be used when wanting to see all errors while attempting to // Should be used when wanting to see all errors while attempting to
@@ -192,6 +192,23 @@ type Config struct {
// //
EC2MetadataDisableTimeoutOverride *bool EC2MetadataDisableTimeoutOverride *bool
// Set this to `false` to disable EC2Metadata client from falling back to IMDSv1.
// By default, EC2 role credentials will fall back to IMDSv1 as needed for backwards compatibility.
// You can disable this behavior by explicitly setting this flag to `false`. When false, the EC2Metadata
// client will return any errors encountered from attempting to fetch a token instead of silently
// using the insecure data flow of IMDSv1.
//
// Example:
// sess := session.Must(session.NewSession(aws.NewConfig()
// .WithEC2MetadataEnableFallback(false)))
//
// svc := s3.New(sess)
//
// See [configuring IMDS] for more information.
//
// [configuring IMDS]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
EC2MetadataEnableFallback *bool
// Instructs the endpoint to be generated for a service client to // Instructs the endpoint to be generated for a service client to
// be the dual stack endpoint. The dual stack endpoint will support // be the dual stack endpoint. The dual stack endpoint will support
// both IPv4 and IPv6 addressing. // both IPv4 and IPv6 addressing.
@@ -283,16 +300,16 @@ type Config struct {
// NewConfig returns a new Config pointer that can be chained with builder // NewConfig returns a new Config pointer that can be chained with builder
// methods to set multiple configuration values inline without using pointers. // methods to set multiple configuration values inline without using pointers.
// //
// // Create Session with MaxRetries configuration to be shared by multiple // // Create Session with MaxRetries configuration to be shared by multiple
// // service clients. // // service clients.
// sess := session.Must(session.NewSession(aws.NewConfig(). // sess := session.Must(session.NewSession(aws.NewConfig().
// WithMaxRetries(3), // WithMaxRetries(3),
// )) // ))
// //
// // Create S3 service client with a specific Region. // // Create S3 service client with a specific Region.
// svc := s3.New(sess, aws.NewConfig(). // svc := s3.New(sess, aws.NewConfig().
// WithRegion("us-west-2"), // WithRegion("us-west-2"),
// ) // )
func NewConfig() *Config { func NewConfig() *Config {
return &Config{} return &Config{}
} }
@@ -432,6 +449,13 @@ func (c *Config) WithEC2MetadataDisableTimeoutOverride(enable bool) *Config {
return c return c
} }
// WithEC2MetadataEnableFallback sets a config EC2MetadataEnableFallback value
// returning a Config pointer for chaining.
func (c *Config) WithEC2MetadataEnableFallback(v bool) *Config {
c.EC2MetadataEnableFallback = &v
return c
}
// WithSleepDelay overrides the function used to sleep while waiting for the // WithSleepDelay overrides the function used to sleep while waiting for the
// next retry. Defaults to time.Sleep. // next retry. Defaults to time.Sleep.
func (c *Config) WithSleepDelay(fn func(time.Duration)) *Config { func (c *Config) WithSleepDelay(fn func(time.Duration)) *Config {
@@ -576,6 +600,10 @@ func mergeInConfig(dst *Config, other *Config) {
dst.EC2MetadataDisableTimeoutOverride = other.EC2MetadataDisableTimeoutOverride dst.EC2MetadataDisableTimeoutOverride = other.EC2MetadataDisableTimeoutOverride
} }
if other.EC2MetadataEnableFallback != nil {
dst.EC2MetadataEnableFallback = other.EC2MetadataEnableFallback
}
if other.SleepDelay != nil { if other.SleepDelay != nil {
dst.SleepDelay = other.SleepDelay dst.SleepDelay = other.SleepDelay
} }
+18 -6
View File
@@ -226,12 +226,24 @@ func NewCredentialsCommand(command *exec.Cmd, options ...func(*ProcessProvider))
return credentials.NewCredentials(p) return credentials.NewCredentials(p)
} }
type credentialProcessResponse struct { // A CredentialProcessResponse is the AWS credentials format that must be
Version int // returned when executing an external credential_process.
AccessKeyID string `json:"AccessKeyId"` type CredentialProcessResponse struct {
// As of this writing, the Version key must be set to 1. This might
// increment over time as the structure evolves.
Version int
// The access key ID that identifies the temporary security credentials.
AccessKeyID string `json:"AccessKeyId"`
// The secret access key that can be used to sign requests.
SecretAccessKey string SecretAccessKey string
SessionToken string
Expiration *time.Time // The token that users must pass to the service API to use the temporary credentials.
SessionToken string
// The date on which the current credentials expire.
Expiration *time.Time
} }
// Retrieve executes the 'credential_process' and returns the credentials. // Retrieve executes the 'credential_process' and returns the credentials.
@@ -242,7 +254,7 @@ func (p *ProcessProvider) Retrieve() (credentials.Value, error) {
} }
// Serialize and validate response // Serialize and validate response
resp := &credentialProcessResponse{} resp := &CredentialProcessResponse{}
if err = json.Unmarshal(out, resp); err != nil { if err = json.Unmarshal(out, resp); err != nil {
return credentials.Value{ProviderName: ProviderName}, awserr.New( return credentials.Value{ProviderName: ProviderName}, awserr.New(
ErrCodeProcessProviderParse, ErrCodeProcessProviderParse,
@@ -9,7 +9,7 @@ to refresh the credentials will be synchronized. But, the SDK is unable to
ensure synchronous usage of the AssumeRoleProvider if the value is shared ensure synchronous usage of the AssumeRoleProvider if the value is shared
between multiple Credentials, Sessions or service clients. between multiple Credentials, Sessions or service clients.
Assume Role # Assume Role
To assume an IAM role using STS with the SDK you can create a new Credentials To assume an IAM role using STS with the SDK you can create a new Credentials
with the SDKs's stscreds package. with the SDKs's stscreds package.
@@ -27,7 +27,7 @@ with the SDKs's stscreds package.
// from assumed role. // from assumed role.
svc := s3.New(sess, &aws.Config{Credentials: creds}) svc := s3.New(sess, &aws.Config{Credentials: creds})
Assume Role with static MFA Token # Assume Role with static MFA Token
To assume an IAM role with a MFA token you can either specify a MFA token code To assume an IAM role with a MFA token you can either specify a MFA token code
directly or provide a function to prompt the user each time the credentials directly or provide a function to prompt the user each time the credentials
@@ -49,7 +49,7 @@ credentials.
// from assumed role. // from assumed role.
svc := s3.New(sess, &aws.Config{Credentials: creds}) svc := s3.New(sess, &aws.Config{Credentials: creds})
Assume Role with MFA Token Provider # Assume Role with MFA Token Provider
To assume an IAM role with MFA for longer running tasks where the credentials To assume an IAM role with MFA for longer running tasks where the credentials
may need to be refreshed setting the TokenProvider field of AssumeRoleProvider may need to be refreshed setting the TokenProvider field of AssumeRoleProvider
@@ -74,7 +74,6 @@ single Credentials with an AssumeRoleProvider can be shared safely.
// Create service client value configured for credentials // Create service client value configured for credentials
// from assumed role. // from assumed role.
svc := s3.New(sess, &aws.Config{Credentials: creds}) svc := s3.New(sess, &aws.Config{Credentials: creds})
*/ */
package stscreds package stscreds
@@ -199,6 +198,10 @@ type AssumeRoleProvider struct {
// or an Amazon Resource Name (ARN) for a virtual device (such as arn:aws:iam::123456789012:mfa/user). // or an Amazon Resource Name (ARN) for a virtual device (such as arn:aws:iam::123456789012:mfa/user).
SerialNumber *string SerialNumber *string
// The SourceIdentity which is used to identity a persistent identity through the whole session.
// For more details see https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_control-access_monitor.html
SourceIdentity *string
// The value provided by the MFA device, if the trust policy of the role being // The value provided by the MFA device, if the trust policy of the role being
// assumed requires MFA (that is, if the policy includes a condition that tests // assumed requires MFA (that is, if the policy includes a condition that tests
// for MFA). If the role being assumed requires MFA and if the TokenCode value // for MFA). If the role being assumed requires MFA and if the TokenCode value
@@ -320,6 +323,7 @@ func (p *AssumeRoleProvider) RetrieveWithContext(ctx credentials.Context) (crede
Tags: p.Tags, Tags: p.Tags,
PolicyArns: p.PolicyArns, PolicyArns: p.PolicyArns,
TransitiveTagKeys: p.TransitiveTagKeys, TransitiveTagKeys: p.TransitiveTagKeys,
SourceIdentity: p.SourceIdentity,
} }
if p.Policy != nil { if p.Policy != nil {
input.Policy = p.Policy input.Policy = p.Policy
+5 -5
View File
@@ -57,13 +57,13 @@ type EC2Metadata struct {
// New creates a new instance of the EC2Metadata client with a session. // New creates a new instance of the EC2Metadata client with a session.
// This client is safe to use across multiple goroutines. // This client is safe to use across multiple goroutines.
// //
//
// Example: // Example:
// // Create a EC2Metadata client from just a session.
// svc := ec2metadata.New(mySession)
// //
// // Create a EC2Metadata client with additional configuration // // Create a EC2Metadata client from just a session.
// svc := ec2metadata.New(mySession, aws.NewConfig().WithLogLevel(aws.LogDebugHTTPBody)) // svc := ec2metadata.New(mySession)
//
// // Create a EC2Metadata client with additional configuration
// svc := ec2metadata.New(mySession, aws.NewConfig().WithLogLevel(aws.LogDebugHTTPBody))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *EC2Metadata { func New(p client.ConfigProvider, cfgs ...*aws.Config) *EC2Metadata {
c := p.ClientConfig(ServiceName, cfgs...) c := p.ClientConfig(ServiceName, cfgs...)
return NewClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion) return NewClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion)
+14 -11
View File
@@ -1,6 +1,7 @@
package ec2metadata package ec2metadata
import ( import (
"fmt"
"net/http" "net/http"
"sync/atomic" "sync/atomic"
"time" "time"
@@ -33,11 +34,15 @@ func newTokenProvider(c *EC2Metadata, duration time.Duration) *tokenProvider {
return &tokenProvider{client: c, configuredTTL: duration} return &tokenProvider{client: c, configuredTTL: duration}
} }
// check if fallback is enabled
func (t *tokenProvider) fallbackEnabled() bool {
return t.client.Config.EC2MetadataEnableFallback == nil || *t.client.Config.EC2MetadataEnableFallback
}
// fetchTokenHandler fetches token for EC2Metadata service client by default. // fetchTokenHandler fetches token for EC2Metadata service client by default.
func (t *tokenProvider) fetchTokenHandler(r *request.Request) { func (t *tokenProvider) fetchTokenHandler(r *request.Request) {
// short-circuits to insecure data flow if tokenProvider is disabled. // short-circuits to insecure data flow if tokenProvider is disabled.
if v := atomic.LoadUint32(&t.disabled); v == 1 { if v := atomic.LoadUint32(&t.disabled); v == 1 && t.fallbackEnabled() {
return return
} }
@@ -49,23 +54,21 @@ func (t *tokenProvider) fetchTokenHandler(r *request.Request) {
output, err := t.client.getToken(r.Context(), t.configuredTTL) output, err := t.client.getToken(r.Context(), t.configuredTTL)
if err != nil { if err != nil {
// only attempt fallback to insecure data flow if IMDSv1 is enabled
if !t.fallbackEnabled() {
r.Error = awserr.New("EC2MetadataError", "failed to get IMDSv2 token and fallback to IMDSv1 is disabled", err)
return
}
// change the disabled flag on token provider to true, // change the disabled flag on token provider to true and fallback
// when error is request timeout error.
if requestFailureError, ok := err.(awserr.RequestFailure); ok { if requestFailureError, ok := err.(awserr.RequestFailure); ok {
switch requestFailureError.StatusCode() { switch requestFailureError.StatusCode() {
case http.StatusForbidden, http.StatusNotFound, http.StatusMethodNotAllowed: case http.StatusForbidden, http.StatusNotFound, http.StatusMethodNotAllowed:
atomic.StoreUint32(&t.disabled, 1) atomic.StoreUint32(&t.disabled, 1)
t.client.Config.Logger.Log(fmt.Sprintf("WARN: failed to get session token, falling back to IMDSv1: %v", requestFailureError))
case http.StatusBadRequest: case http.StatusBadRequest:
r.Error = requestFailureError r.Error = requestFailureError
} }
// Check if request timed out while waiting for response
if e, ok := requestFailureError.OrigErr().(awserr.Error); ok {
if e.Code() == request.ErrCodeRequestError {
atomic.StoreUint32(&t.disabled, 1)
}
}
} }
return return
} }
File diff suppressed because it is too large Load Diff
+30 -30
View File
@@ -174,7 +174,6 @@ const (
// Options provides the means to control how a Session is created and what // Options provides the means to control how a Session is created and what
// configuration values will be loaded. // configuration values will be loaded.
//
type Options struct { type Options struct {
// Provides config values for the SDK to use when creating service clients // Provides config values for the SDK to use when creating service clients
// and making API requests to services. Any value set in with this field // and making API requests to services. Any value set in with this field
@@ -224,7 +223,7 @@ type Options struct {
// from stdin for the MFA token code. // from stdin for the MFA token code.
// //
// This field is only used if the shared configuration is enabled, and // This field is only used if the shared configuration is enabled, and
// the config enables assume role wit MFA via the mfa_serial field. // the config enables assume role with MFA via the mfa_serial field.
AssumeRoleTokenProvider func() (string, error) AssumeRoleTokenProvider func() (string, error)
// When the SDK's shared config is configured to assume a role this option // When the SDK's shared config is configured to assume a role this option
@@ -322,24 +321,24 @@ type Options struct {
// credentials file. Enabling the Shared Config will also allow the Session // credentials file. Enabling the Shared Config will also allow the Session
// to be built with retrieving credentials with AssumeRole set in the config. // to be built with retrieving credentials with AssumeRole set in the config.
// //
// // Equivalent to session.New // // Equivalent to session.New
// sess := session.Must(session.NewSessionWithOptions(session.Options{})) // sess := session.Must(session.NewSessionWithOptions(session.Options{}))
// //
// // Specify profile to load for the session's config // // Specify profile to load for the session's config
// sess := session.Must(session.NewSessionWithOptions(session.Options{ // sess := session.Must(session.NewSessionWithOptions(session.Options{
// Profile: "profile_name", // Profile: "profile_name",
// })) // }))
// //
// // Specify profile for config and region for requests // // Specify profile for config and region for requests
// sess := session.Must(session.NewSessionWithOptions(session.Options{ // sess := session.Must(session.NewSessionWithOptions(session.Options{
// Config: aws.Config{Region: aws.String("us-east-1")}, // Config: aws.Config{Region: aws.String("us-east-1")},
// Profile: "profile_name", // Profile: "profile_name",
// })) // }))
// //
// // Force enable Shared Config support // // Force enable Shared Config support
// sess := session.Must(session.NewSessionWithOptions(session.Options{ // sess := session.Must(session.NewSessionWithOptions(session.Options{
// SharedConfigState: session.SharedConfigEnable, // SharedConfigState: session.SharedConfigEnable,
// })) // }))
func NewSessionWithOptions(opts Options) (*Session, error) { func NewSessionWithOptions(opts Options) (*Session, error) {
var envCfg envConfig var envCfg envConfig
var err error var err error
@@ -375,7 +374,7 @@ func NewSessionWithOptions(opts Options) (*Session, error) {
// This helper is intended to be used in variable initialization to load the // This helper is intended to be used in variable initialization to load the
// Session and configuration at startup. Such as: // Session and configuration at startup. Such as:
// //
// var sess = session.Must(session.NewSession()) // var sess = session.Must(session.NewSession())
func Must(sess *Session, err error) *Session { func Must(sess *Session, err error) *Session {
if err != nil { if err != nil {
panic(err) panic(err)
@@ -780,16 +779,6 @@ func mergeConfigSrcs(cfg, userCfg *aws.Config,
cfg.EndpointResolver = wrapEC2IMDSEndpoint(cfg.EndpointResolver, ec2IMDSEndpoint, endpointMode) cfg.EndpointResolver = wrapEC2IMDSEndpoint(cfg.EndpointResolver, ec2IMDSEndpoint, endpointMode)
} }
// Configure credentials if not already set by the user when creating the
// Session.
if cfg.Credentials == credentials.AnonymousCredentials && userCfg.Credentials == nil {
creds, err := resolveCredentials(cfg, envCfg, sharedCfg, handlers, sessOpts)
if err != nil {
return err
}
cfg.Credentials = creds
}
cfg.S3UseARNRegion = userCfg.S3UseARNRegion cfg.S3UseARNRegion = userCfg.S3UseARNRegion
if cfg.S3UseARNRegion == nil { if cfg.S3UseARNRegion == nil {
cfg.S3UseARNRegion = &envCfg.S3UseARNRegion cfg.S3UseARNRegion = &envCfg.S3UseARNRegion
@@ -812,6 +801,17 @@ func mergeConfigSrcs(cfg, userCfg *aws.Config,
} }
} }
// Configure credentials if not already set by the user when creating the Session.
// Credentials are resolved last such that all _resolved_ config values are propagated to credential providers.
// ticket: P83606045
if cfg.Credentials == credentials.AnonymousCredentials && userCfg.Credentials == nil {
creds, err := resolveCredentials(cfg, envCfg, sharedCfg, handlers, sessOpts)
if err != nil {
return err
}
cfg.Credentials = creds
}
return nil return nil
} }
@@ -845,8 +845,8 @@ func initHandlers(s *Session) {
// and handlers. If any additional configs are provided they will be merged // and handlers. If any additional configs are provided they will be merged
// on top of the Session's copied config. // on top of the Session's copied config.
// //
// // Create a copy of the current Session, configured for the us-west-2 region. // // Create a copy of the current Session, configured for the us-west-2 region.
// sess.Copy(&aws.Config{Region: aws.String("us-west-2")}) // sess.Copy(&aws.Config{Region: aws.String("us-west-2")})
func (s *Session) Copy(cfgs ...*aws.Config) *Session { func (s *Session) Copy(cfgs ...*aws.Config) *Session {
newSession := &Session{ newSession := &Session{
Config: s.Config.Copy(cfgs...), Config: s.Config.Copy(cfgs...),
+6 -5
View File
@@ -3,7 +3,7 @@
// Provides request signing for request that need to be signed with // Provides request signing for request that need to be signed with
// AWS V4 Signatures. // AWS V4 Signatures.
// //
// Standalone Signer // # Standalone Signer
// //
// Generally using the signer outside of the SDK should not require any additional // Generally using the signer outside of the SDK should not require any additional
// logic when using Go v1.5 or higher. The signer does this by taking advantage // logic when using Go v1.5 or higher. The signer does this by taking advantage
@@ -14,10 +14,10 @@
// The signer will first check the URL.Opaque field, and use its value if set. // The signer will first check the URL.Opaque field, and use its value if set.
// The signer does require the URL.Opaque field to be set in the form of: // The signer does require the URL.Opaque field to be set in the form of:
// //
// "//<hostname>/<path>" // "//<hostname>/<path>"
// //
// // e.g. // // e.g.
// "//example.com/some/path" // "//example.com/some/path"
// //
// The leading "//" and hostname are required or the URL.Opaque escaping will // The leading "//" and hostname are required or the URL.Opaque escaping will
// not work correctly. // not work correctly.
@@ -695,7 +695,8 @@ func (ctx *signingCtx) buildBodyDigest() error {
includeSHA256Header := ctx.unsignedPayload || includeSHA256Header := ctx.unsignedPayload ||
ctx.ServiceName == "s3" || ctx.ServiceName == "s3" ||
ctx.ServiceName == "s3-object-lambda" || ctx.ServiceName == "s3-object-lambda" ||
ctx.ServiceName == "glacier" ctx.ServiceName == "glacier" ||
ctx.ServiceName == "s3-outposts"
s3Presign := ctx.isPresign && s3Presign := ctx.isPresign &&
(ctx.ServiceName == "s3" || (ctx.ServiceName == "s3" ||
+1 -1
View File
@@ -5,4 +5,4 @@ package aws
const SDKName = "aws-sdk-go" const SDKName = "aws-sdk-go"
// SDKVersion is the version of this SDK // SDKVersion is the version of this SDK
const SDKVersion = "1.44.181" const SDKVersion = "1.44.294"
@@ -22,7 +22,7 @@ const (
// UnmarshalTypedError provides unmarshaling errors API response errors // UnmarshalTypedError provides unmarshaling errors API response errors
// for both typed and untyped errors. // for both typed and untyped errors.
type UnmarshalTypedError struct { type UnmarshalTypedError struct {
exceptions map[string]func(protocol.ResponseMetadata) error exceptions map[string]func(protocol.ResponseMetadata) error
queryExceptions map[string]func(protocol.ResponseMetadata, string) error queryExceptions map[string]func(protocol.ResponseMetadata, string) error
} }
@@ -30,11 +30,13 @@ type UnmarshalTypedError struct {
// set of exception names to the error unmarshalers // set of exception names to the error unmarshalers
func NewUnmarshalTypedError(exceptions map[string]func(protocol.ResponseMetadata) error) *UnmarshalTypedError { func NewUnmarshalTypedError(exceptions map[string]func(protocol.ResponseMetadata) error) *UnmarshalTypedError {
return &UnmarshalTypedError{ return &UnmarshalTypedError{
exceptions: exceptions, exceptions: exceptions,
queryExceptions: map[string]func(protocol.ResponseMetadata, string) error{}, queryExceptions: map[string]func(protocol.ResponseMetadata, string) error{},
} }
} }
// NewUnmarshalTypedErrorWithOptions works similar to NewUnmarshalTypedError applying options to the UnmarshalTypedError
// before returning it
func NewUnmarshalTypedErrorWithOptions(exceptions map[string]func(protocol.ResponseMetadata) error, optFns ...func(*UnmarshalTypedError)) *UnmarshalTypedError { func NewUnmarshalTypedErrorWithOptions(exceptions map[string]func(protocol.ResponseMetadata) error, optFns ...func(*UnmarshalTypedError)) *UnmarshalTypedError {
unmarshaledError := NewUnmarshalTypedError(exceptions) unmarshaledError := NewUnmarshalTypedError(exceptions)
for _, fn := range optFns { for _, fn := range optFns {
@@ -43,6 +45,11 @@ func NewUnmarshalTypedErrorWithOptions(exceptions map[string]func(protocol.Respo
return unmarshaledError return unmarshaledError
} }
// WithQueryCompatibility is a helper function to construct a functional option for use with NewUnmarshalTypedErrorWithOptions.
// The queryExceptions given act as an override for unmarshalling errors when query compatible error codes are found.
// See also [awsQueryCompatible trait]
//
// [awsQueryCompatible trait]: https://smithy.io/2.0/aws/protocols/aws-query-protocol.html#aws-protocols-awsquerycompatible-trait
func WithQueryCompatibility(queryExceptions map[string]func(protocol.ResponseMetadata, string) error) func(*UnmarshalTypedError) { func WithQueryCompatibility(queryExceptions map[string]func(protocol.ResponseMetadata, string) error) func(*UnmarshalTypedError) {
return func(typedError *UnmarshalTypedError) { return func(typedError *UnmarshalTypedError) {
typedError.queryExceptions = queryExceptions typedError.queryExceptions = queryExceptions
+4
View File
@@ -287,6 +287,10 @@ func convertType(v reflect.Value, tag reflect.StructTag) (str string, err error)
if tag.Get("location") != "header" || tag.Get("enum") == "" { if tag.Get("location") != "header" || tag.Get("enum") == "" {
return "", fmt.Errorf("%T is only supported with location header and enum shapes", value) return "", fmt.Errorf("%T is only supported with location header and enum shapes", value)
} }
if len(value) == 0 {
return "", errValueNotSet
}
buff := &bytes.Buffer{} buff := &bytes.Buffer{}
for i, sv := range value { for i, sv := range value {
if sv == nil || len(*sv) == 0 { if sv == nil || len(*sv) == 0 {
@@ -2,6 +2,7 @@ package restjson
import ( import (
"bytes" "bytes"
"encoding/json"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@@ -40,52 +41,30 @@ func (u *UnmarshalTypedError) UnmarshalError(
resp *http.Response, resp *http.Response,
respMeta protocol.ResponseMetadata, respMeta protocol.ResponseMetadata,
) (error, error) { ) (error, error) {
code, msg, err := unmarshalErrorInfo(resp)
code := resp.Header.Get(errorTypeHeader) if err != nil {
msg := resp.Header.Get(errorMessageHeader) return nil, err
body := resp.Body
if len(code) == 0 {
// If unable to get code from HTTP headers have to parse JSON message
// to determine what kind of exception this will be.
var buf bytes.Buffer
var jsonErr jsonErrorResponse
teeReader := io.TeeReader(resp.Body, &buf)
err := jsonutil.UnmarshalJSONError(&jsonErr, teeReader)
if err != nil {
return nil, err
}
body = ioutil.NopCloser(&buf)
code = jsonErr.Code
msg = jsonErr.Message
} }
// If code has colon separators remove them so can compare against modeled fn, ok := u.exceptions[code]
// exception names. if !ok {
code = strings.SplitN(code, ":", 2)[0] return awserr.NewRequestFailure(
awserr.New(code, msg, nil),
if fn, ok := u.exceptions[code]; ok { respMeta.StatusCode,
// If exception code is know, use associated constructor to get a value respMeta.RequestID,
// for the exception that the JSON body can be unmarshaled into. ), nil
v := fn(respMeta)
if err := jsonutil.UnmarshalJSONCaseInsensitive(v, body); err != nil {
return nil, err
}
if err := rest.UnmarshalResponse(resp, v, true); err != nil {
return nil, err
}
return v, nil
} }
// fallback to unmodeled generic exceptions v := fn(respMeta)
return awserr.NewRequestFailure( if err := jsonutil.UnmarshalJSONCaseInsensitive(v, resp.Body); err != nil {
awserr.New(code, msg, nil), return nil, err
respMeta.StatusCode, }
respMeta.RequestID,
), nil if err := rest.UnmarshalResponse(resp, v, true); err != nil {
return nil, err
}
return v, nil
} }
// UnmarshalErrorHandler is a named request handler for unmarshaling restjson // UnmarshalErrorHandler is a named request handler for unmarshaling restjson
@@ -99,36 +78,80 @@ var UnmarshalErrorHandler = request.NamedHandler{
func UnmarshalError(r *request.Request) { func UnmarshalError(r *request.Request) {
defer r.HTTPResponse.Body.Close() defer r.HTTPResponse.Body.Close()
var jsonErr jsonErrorResponse code, msg, err := unmarshalErrorInfo(r.HTTPResponse)
err := jsonutil.UnmarshalJSONError(&jsonErr, r.HTTPResponse.Body)
if err != nil { if err != nil {
r.Error = awserr.NewRequestFailure( r.Error = awserr.NewRequestFailure(
awserr.New(request.ErrCodeSerialization, awserr.New(request.ErrCodeSerialization, "failed to unmarshal response error", err),
"failed to unmarshal response error", err),
r.HTTPResponse.StatusCode, r.HTTPResponse.StatusCode,
r.RequestID, r.RequestID,
) )
return return
} }
code := r.HTTPResponse.Header.Get(errorTypeHeader)
if code == "" {
code = jsonErr.Code
}
msg := r.HTTPResponse.Header.Get(errorMessageHeader)
if msg == "" {
msg = jsonErr.Message
}
code = strings.SplitN(code, ":", 2)[0]
r.Error = awserr.NewRequestFailure( r.Error = awserr.NewRequestFailure(
awserr.New(code, jsonErr.Message, nil), awserr.New(code, msg, nil),
r.HTTPResponse.StatusCode, r.HTTPResponse.StatusCode,
r.RequestID, r.RequestID,
) )
} }
type jsonErrorResponse struct { type jsonErrorResponse struct {
Type string `json:"__type"`
Code string `json:"code"` Code string `json:"code"`
Message string `json:"message"` Message string `json:"message"`
} }
func (j *jsonErrorResponse) SanitizedCode() string {
code := j.Code
if len(j.Type) > 0 {
code = j.Type
}
return sanitizeCode(code)
}
// Remove superfluous components from a restJson error code.
// - If a : character is present, then take only the contents before the
// first : character in the value.
// - If a # character is present, then take only the contents after the first
// # character in the value.
//
// All of the following error values resolve to FooError:
// - FooError
// - FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/
// - aws.protocoltests.restjson#FooError
// - aws.protocoltests.restjson#FooError:http://internal.amazon.com/coral/com.amazon.coral.validate/
func sanitizeCode(code string) string {
noColon := strings.SplitN(code, ":", 2)[0]
hashSplit := strings.SplitN(noColon, "#", 2)
return hashSplit[len(hashSplit)-1]
}
// attempt to garner error details from the response, preferring header values
// when present
func unmarshalErrorInfo(resp *http.Response) (code string, msg string, err error) {
code = sanitizeCode(resp.Header.Get(errorTypeHeader))
msg = resp.Header.Get(errorMessageHeader)
if len(code) > 0 && len(msg) > 0 {
return
}
// a modeled error will have to be re-deserialized later, so the body must
// be preserved
var buf bytes.Buffer
tee := io.TeeReader(resp.Body, &buf)
defer func() { resp.Body = ioutil.NopCloser(&buf) }()
var jsonErr jsonErrorResponse
if decodeErr := json.NewDecoder(tee).Decode(&jsonErr); decodeErr != nil && decodeErr != io.EOF {
err = awserr.NewUnmarshalError(decodeErr, "failed to decode response body", buf.Bytes())
return
}
if len(code) == 0 {
code = jsonErr.SanitizedCode()
}
if len(msg) == 0 {
msg = jsonErr.Message
}
return
}
+1131 -693
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -25,5 +25,5 @@ func add100Continue(r *request.Request) {
return return
} }
r.HTTPRequest.Header.Set("Expect", "100-Continue") r.HTTPRequest.Header.Set("Expect", "100-continue")
} }
+24 -22
View File
@@ -40,21 +40,21 @@ type UploadInput struct {
// information about access point ARNs, see Using access points (https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-access-points.html) // information about access point ARNs, see Using access points (https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-access-points.html)
// in the Amazon S3 User Guide. // in the Amazon S3 User Guide.
// //
// When using this action with Amazon S3 on Outposts, you must direct requests // When you use this action with Amazon S3 on Outposts, you must direct requests
// to the S3 on Outposts hostname. The S3 on Outposts hostname takes the form // to the S3 on Outposts hostname. The S3 on Outposts hostname takes the form
// AccessPointName-AccountId.outpostID.s3-outposts.Region.amazonaws.com. When // AccessPointName-AccountId.outpostID.s3-outposts.Region.amazonaws.com. When
// using this action with S3 on Outposts through the Amazon Web Services SDKs, // you use this action with S3 on Outposts through the Amazon Web Services SDKs,
// you provide the Outposts bucket ARN in place of the bucket name. For more // you provide the Outposts access point ARN in place of the bucket name. For
// information about S3 on Outposts ARNs, see Using Amazon S3 on Outposts (https://docs.aws.amazon.com/AmazonS3/latest/userguide/S3onOutposts.html) // more information about S3 on Outposts ARNs, see What is S3 on Outposts? (https://docs.aws.amazon.com/AmazonS3/latest/userguide/S3onOutposts.html)
// in the Amazon S3 User Guide. // in the Amazon S3 User Guide.
// //
// Bucket is a required field // Bucket is a required field
Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"` Bucket *string `location:"uri" locationName:"Bucket" type:"string" required:"true"`
// Specifies whether Amazon S3 should use an S3 Bucket Key for object encryption // Specifies whether Amazon S3 should use an S3 Bucket Key for object encryption
// with server-side encryption using AWS KMS (SSE-KMS). Setting this header // with server-side encryption using Key Management Service (KMS) keys (SSE-KMS).
// to true causes Amazon S3 to use an S3 Bucket Key for object encryption with // Setting this header to true causes Amazon S3 to use an S3 Bucket Key for
// SSE-KMS. // object encryption with SSE-KMS.
// //
// Specifying this header with a PUT action doesnt affect bucket-level settings // Specifying this header with a PUT action doesnt affect bucket-level settings
// for S3 Bucket Key. // for S3 Bucket Key.
@@ -111,13 +111,13 @@ type UploadInput struct {
ChecksumSHA256 *string `location:"header" locationName:"x-amz-checksum-sha256" type:"string"` ChecksumSHA256 *string `location:"header" locationName:"x-amz-checksum-sha256" type:"string"`
// Specifies presentational information for the object. For more information, // Specifies presentational information for the object. For more information,
// see http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1). // see https://www.rfc-editor.org/rfc/rfc6266#section-4 (https://www.rfc-editor.org/rfc/rfc6266#section-4).
ContentDisposition *string `location:"header" locationName:"Content-Disposition" type:"string"` ContentDisposition *string `location:"header" locationName:"Content-Disposition" type:"string"`
// Specifies what content encodings have been applied to the object and thus // Specifies what content encodings have been applied to the object and thus
// what decoding mechanisms must be applied to obtain the media-type referenced // what decoding mechanisms must be applied to obtain the media-type referenced
// by the Content-Type header field. For more information, see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 // by the Content-Type header field. For more information, see https://www.rfc-editor.org/rfc/rfc9110.html#field.content-encoding
// (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11). // (https://www.rfc-editor.org/rfc/rfc9110.html#field.content-encoding).
ContentEncoding *string `location:"header" locationName:"Content-Encoding" type:"string"` ContentEncoding *string `location:"header" locationName:"Content-Encoding" type:"string"`
// The language the content is in. // The language the content is in.
@@ -135,7 +135,7 @@ type UploadInput struct {
ContentMD5 *string `location:"header" locationName:"Content-MD5" type:"string"` ContentMD5 *string `location:"header" locationName:"Content-MD5" type:"string"`
// A standard MIME type describing the format of the contents. For more information, // A standard MIME type describing the format of the contents. For more information,
// see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17). // see https://www.rfc-editor.org/rfc/rfc9110.html#name-content-type (https://www.rfc-editor.org/rfc/rfc9110.html#name-content-type).
ContentType *string `location:"header" locationName:"Content-Type" type:"string"` ContentType *string `location:"header" locationName:"Content-Type" type:"string"`
// The account ID of the expected bucket owner. If the bucket is owned by a // The account ID of the expected bucket owner. If the bucket is owned by a
@@ -144,7 +144,7 @@ type UploadInput struct {
ExpectedBucketOwner *string `location:"header" locationName:"x-amz-expected-bucket-owner" type:"string"` ExpectedBucketOwner *string `location:"header" locationName:"x-amz-expected-bucket-owner" type:"string"`
// The date and time at which the object is no longer cacheable. For more information, // The date and time at which the object is no longer cacheable. For more information,
// see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21). // see https://www.rfc-editor.org/rfc/rfc7234#section-5.3 (https://www.rfc-editor.org/rfc/rfc7234#section-5.3).
Expires *time.Time `location:"header" locationName:"Expires" type:"timestamp"` Expires *time.Time `location:"header" locationName:"Expires" type:"timestamp"`
// Gives the grantee READ, READ_ACP, and WRITE_ACP permissions on the object. // Gives the grantee READ, READ_ACP, and WRITE_ACP permissions on the object.
@@ -211,21 +211,23 @@ type UploadInput struct {
// Specifies the Amazon Web Services KMS Encryption Context to use for object // Specifies the Amazon Web Services KMS Encryption Context to use for object
// encryption. The value of this header is a base64-encoded UTF-8 string holding // encryption. The value of this header is a base64-encoded UTF-8 string holding
// JSON with the encryption context key-value pairs. // JSON with the encryption context key-value pairs. This value is stored as
// object metadata and automatically gets passed on to Amazon Web Services KMS
// for future GetObject or CopyObject operations on this object.
SSEKMSEncryptionContext *string `location:"header" locationName:"x-amz-server-side-encryption-context" type:"string" sensitive:"true"` SSEKMSEncryptionContext *string `location:"header" locationName:"x-amz-server-side-encryption-context" type:"string" sensitive:"true"`
// If x-amz-server-side-encryption is present and has the value of aws:kms, // If x-amz-server-side-encryption has a valid value of aws:kms or aws:kms:dsse,
// this header specifies the ID of the Amazon Web Services Key Management Service // this header specifies the ID of the Key Management Service (KMS) symmetric
// (Amazon Web Services KMS) symmetrical customer managed key that was used // encryption customer managed key that was used for the object. If you specify
// for the object. If you specify x-amz-server-side-encryption:aws:kms, but // x-amz-server-side-encryption:aws:kms or x-amz-server-side-encryption:aws:kms:dsse,
// do not providex-amz-server-side-encryption-aws-kms-key-id, Amazon S3 uses // but do not providex-amz-server-side-encryption-aws-kms-key-id, Amazon S3
// the Amazon Web Services managed key to protect the data. If the KMS key does // uses the Amazon Web Services managed key (aws/s3) to protect the data. If
// not exist in the same account issuing the command, you must use the full // the KMS key does not exist in the same account that's issuing the command,
// ARN and not just the ID. // you must use the full ARN and not just the ID.
SSEKMSKeyId *string `location:"header" locationName:"x-amz-server-side-encryption-aws-kms-key-id" type:"string" sensitive:"true"` SSEKMSKeyId *string `location:"header" locationName:"x-amz-server-side-encryption-aws-kms-key-id" type:"string" sensitive:"true"`
// The server-side encryption algorithm used when storing this object in Amazon // The server-side encryption algorithm used when storing this object in Amazon
// S3 (for example, AES256, aws:kms). // S3 (for example, AES256, aws:kms, aws:kms:dsse).
ServerSideEncryption *string `location:"header" locationName:"x-amz-server-side-encryption" type:"string" enum:"ServerSideEncryption"` ServerSideEncryption *string `location:"header" locationName:"x-amz-server-side-encryption" type:"string" enum:"ServerSideEncryption"`
// By default, Amazon S3 uses the STANDARD Storage Class to store newly created // By default, Amazon S3 uses the STANDARD Storage Class to store newly created
+81 -71
View File
@@ -56,12 +56,11 @@ func (c *STS) AssumeRoleRequest(input *AssumeRoleInput) (req *request.Request, o
// AssumeRole API operation for AWS Security Token Service. // AssumeRole API operation for AWS Security Token Service.
// //
// Returns a set of temporary security credentials that you can use to access // Returns a set of temporary security credentials that you can use to access
// Amazon Web Services resources that you might not normally have access to. // Amazon Web Services resources. These temporary credentials consist of an
// These temporary credentials consist of an access key ID, a secret access // access key ID, a secret access key, and a security token. Typically, you
// key, and a security token. Typically, you use AssumeRole within your account // use AssumeRole within your account or for cross-account access. For a comparison
// or for cross-account access. For a comparison of AssumeRole with other API // of AssumeRole with other API operations that produce temporary credentials,
// operations that produce temporary credentials, see Requesting Temporary Security // see Requesting Temporary Security Credentials (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html)
// Credentials (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html)
// and Comparing the Amazon Web Services STS API operations (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#stsapi_comparison) // and Comparing the Amazon Web Services STS API operations (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#stsapi_comparison)
// in the IAM User Guide. // in the IAM User Guide.
// //
@@ -86,9 +85,9 @@ func (c *STS) AssumeRoleRequest(input *AssumeRoleInput) (req *request.Request, o
// assumed. For more information, see Session Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session) // assumed. For more information, see Session Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session)
// in the IAM User Guide. // in the IAM User Guide.
// //
// When you create a role, you create two policies: A role trust policy that // When you create a role, you create two policies: a role trust policy that
// specifies who can assume the role and a permissions policy that specifies // specifies who can assume the role, and a permissions policy that specifies
// what can be done with the role. You specify the trusted principal who is // what can be done with the role. You specify the trusted principal that is
// allowed to assume the role in the role trust policy. // allowed to assume the role in the role trust policy.
// //
// To assume a role from a different account, your Amazon Web Services account // To assume a role from a different account, your Amazon Web Services account
@@ -97,9 +96,9 @@ func (c *STS) AssumeRoleRequest(input *AssumeRoleInput) (req *request.Request, o
// are allowed to delegate that access to users in the account. // are allowed to delegate that access to users in the account.
// //
// A user who wants to access a role in a different account must also have permissions // A user who wants to access a role in a different account must also have permissions
// that are delegated from the user account administrator. The administrator // that are delegated from the account administrator. The administrator must
// must attach a policy that allows the user to call AssumeRole for the ARN // attach a policy that allows the user to call AssumeRole for the ARN of the
// of the role in the other account. // role in the other account.
// //
// To allow a user to assume a role in the same account, you can do either of // To allow a user to assume a role in the same account, you can do either of
// the following: // the following:
@@ -518,10 +517,8 @@ func (c *STS) AssumeRoleWithWebIdentityRequest(input *AssumeRoleWithWebIdentityI
// a user. You can also supply the user with a consistent identity throughout // a user. You can also supply the user with a consistent identity throughout
// the lifetime of an application. // the lifetime of an application.
// //
// To learn more about Amazon Cognito, see Amazon Cognito Overview (https://docs.aws.amazon.com/mobile/sdkforandroid/developerguide/cognito-auth.html#d0e840) // To learn more about Amazon Cognito, see Amazon Cognito identity pools (https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-identity.html)
// in Amazon Web Services SDK for Android Developer Guide and Amazon Cognito // in Amazon Cognito Developer Guide.
// Overview (https://docs.aws.amazon.com/mobile/sdkforios/developerguide/cognito-auth.html#d0e664)
// in the Amazon Web Services SDK for iOS Developer Guide.
// //
// Calling AssumeRoleWithWebIdentity does not require the use of Amazon Web // Calling AssumeRoleWithWebIdentity does not require the use of Amazon Web
// Services security credentials. Therefore, you can distribute an application // Services security credentials. Therefore, you can distribute an application
@@ -985,11 +982,11 @@ func (c *STS) GetCallerIdentityRequest(input *GetCallerIdentityInput) (req *requ
// call the operation. // call the operation.
// //
// No permissions are required to perform this operation. If an administrator // No permissions are required to perform this operation. If an administrator
// adds a policy to your IAM user or role that explicitly denies access to the // attaches a policy to your identity that explicitly denies access to the sts:GetCallerIdentity
// sts:GetCallerIdentity action, you can still perform this operation. Permissions // action, you can still perform this operation. Permissions are not required
// are not required because the same information is returned when an IAM user // because the same information is returned when access is denied. To view an
// or role is denied access. To view an example response, see I Am Not Authorized // example response, see I Am Not Authorized to Perform: iam:DeleteVirtualMFADevice
// to Perform: iam:DeleteVirtualMFADevice (https://docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_general.html#troubleshoot_general_access-denied-delete-mfa) // (https://docs.aws.amazon.com/IAM/latest/UserGuide/troubleshoot_general.html#troubleshoot_general_access-denied-delete-mfa)
// in the IAM User Guide. // in the IAM User Guide.
// //
// Returns awserr.Error for service API and SDK errors. Use runtime type assertions // Returns awserr.Error for service API and SDK errors. Use runtime type assertions
@@ -1064,18 +1061,26 @@ func (c *STS) GetFederationTokenRequest(input *GetFederationTokenInput) (req *re
// GetFederationToken API operation for AWS Security Token Service. // GetFederationToken API operation for AWS Security Token Service.
// //
// Returns a set of temporary security credentials (consisting of an access // Returns a set of temporary security credentials (consisting of an access
// key ID, a secret access key, and a security token) for a federated user. // key ID, a secret access key, and a security token) for a user. A typical
// A typical use is in a proxy application that gets temporary security credentials // use is in a proxy application that gets temporary security credentials on
// on behalf of distributed applications inside a corporate network. You must // behalf of distributed applications inside a corporate network.
// call the GetFederationToken operation using the long-term security credentials //
// of an IAM user. As a result, this call is appropriate in contexts where those // You must call the GetFederationToken operation using the long-term security
// credentials can be safely stored, usually in a server-based application. // credentials of an IAM user. As a result, this call is appropriate in contexts
// where those credentials can be safeguarded, usually in a server-based application.
// For a comparison of GetFederationToken with the other API operations that // For a comparison of GetFederationToken with the other API operations that
// produce temporary credentials, see Requesting Temporary Security Credentials // produce temporary credentials, see Requesting Temporary Security Credentials
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html) // (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html)
// and Comparing the Amazon Web Services STS API operations (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#stsapi_comparison) // and Comparing the Amazon Web Services STS API operations (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#stsapi_comparison)
// in the IAM User Guide. // in the IAM User Guide.
// //
// Although it is possible to call GetFederationToken using the security credentials
// of an Amazon Web Services account root user rather than an IAM user that
// you create for the purpose of a proxy application, we do not recommend it.
// For more information, see Safeguard your root user credentials and don't
// use them for everyday tasks (https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#lock-away-credentials)
// in the IAM User Guide.
//
// You can create a mobile-based or browser-based app that can authenticate // You can create a mobile-based or browser-based app that can authenticate
// users using a web identity provider like Login with Amazon, Facebook, Google, // users using a web identity provider like Login with Amazon, Facebook, Google,
// or an OpenID Connect-compatible identity provider. In this case, we recommend // or an OpenID Connect-compatible identity provider. In this case, we recommend
@@ -1084,32 +1089,26 @@ func (c *STS) GetFederationTokenRequest(input *GetFederationTokenInput) (req *re
// (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#api_assumerolewithwebidentity) // (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#api_assumerolewithwebidentity)
// in the IAM User Guide. // in the IAM User Guide.
// //
// You can also call GetFederationToken using the security credentials of an
// Amazon Web Services account root user, but we do not recommend it. Instead,
// we recommend that you create an IAM user for the purpose of the proxy application.
// Then attach a policy to the IAM user that limits federated users to only
// the actions and resources that they need to access. For more information,
// see IAM Best Practices (https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html)
// in the IAM User Guide.
//
// # Session duration // # Session duration
// //
// The temporary credentials are valid for the specified duration, from 900 // The temporary credentials are valid for the specified duration, from 900
// seconds (15 minutes) up to a maximum of 129,600 seconds (36 hours). The default // seconds (15 minutes) up to a maximum of 129,600 seconds (36 hours). The default
// session duration is 43,200 seconds (12 hours). Temporary credentials obtained // session duration is 43,200 seconds (12 hours). Temporary credentials obtained
// by using the Amazon Web Services account root user credentials have a maximum // by using the root user credentials have a maximum duration of 3,600 seconds
// duration of 3,600 seconds (1 hour). // (1 hour).
// //
// # Permissions // # Permissions
// //
// You can use the temporary credentials created by GetFederationToken in any // You can use the temporary credentials created by GetFederationToken in any
// Amazon Web Services service except the following: // Amazon Web Services service with the following exceptions:
// //
// - You cannot call any IAM operations using the CLI or the Amazon Web Services // - You cannot call any IAM operations using the CLI or the Amazon Web Services
// API. // API. This limitation does not apply to console sessions.
// //
// - You cannot call any STS operations except GetCallerIdentity. // - You cannot call any STS operations except GetCallerIdentity.
// //
// You can use temporary credentials for single sign-on (SSO) to the console.
//
// You must pass an inline or managed session policy (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session) // You must pass an inline or managed session policy (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session)
// to this operation. You can pass a single JSON policy document to use as an // to this operation. You can pass a single JSON policy document to use as an
// inline session policy. You can also specify up to 10 managed policy Amazon // inline session policy. You can also specify up to 10 managed policy Amazon
@@ -1266,12 +1265,13 @@ func (c *STS) GetSessionTokenRequest(input *GetSessionTokenInput) (req *request.
// or IAM user. The credentials consist of an access key ID, a secret access // or IAM user. The credentials consist of an access key ID, a secret access
// key, and a security token. Typically, you use GetSessionToken if you want // key, and a security token. Typically, you use GetSessionToken if you want
// to use MFA to protect programmatic calls to specific Amazon Web Services // to use MFA to protect programmatic calls to specific Amazon Web Services
// API operations like Amazon EC2 StopInstances. MFA-enabled IAM users would // API operations like Amazon EC2 StopInstances.
// need to call GetSessionToken and submit an MFA code that is associated with //
// their MFA device. Using the temporary security credentials that are returned // MFA-enabled IAM users must call GetSessionToken and submit an MFA code that
// from the call, IAM users can then make programmatic calls to API operations // is associated with their MFA device. Using the temporary security credentials
// that require MFA authentication. If you do not supply a correct MFA code, // that the call returns, IAM users can then make programmatic calls to API
// then the API returns an access denied error. For a comparison of GetSessionToken // operations that require MFA authentication. An incorrect MFA code causes
// the API to return an access denied error. For a comparison of GetSessionToken
// with the other API operations that produce temporary credentials, see Requesting // with the other API operations that produce temporary credentials, see Requesting
// Temporary Security Credentials (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html) // Temporary Security Credentials (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html)
// and Comparing the Amazon Web Services STS API operations (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#stsapi_comparison) // and Comparing the Amazon Web Services STS API operations (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#stsapi_comparison)
@@ -1286,13 +1286,12 @@ func (c *STS) GetSessionTokenRequest(input *GetSessionTokenInput) (req *request.
// # Session Duration // # Session Duration
// //
// The GetSessionToken operation must be called by using the long-term Amazon // The GetSessionToken operation must be called by using the long-term Amazon
// Web Services security credentials of the Amazon Web Services account root // Web Services security credentials of an IAM user. Credentials that are created
// user or an IAM user. Credentials that are created by IAM users are valid // by IAM users are valid for the duration that you specify. This duration can
// for the duration that you specify. This duration can range from 900 seconds // range from 900 seconds (15 minutes) up to a maximum of 129,600 seconds (36
// (15 minutes) up to a maximum of 129,600 seconds (36 hours), with a default // hours), with a default of 43,200 seconds (12 hours). Credentials based on
// of 43,200 seconds (12 hours). Credentials based on account credentials can // account credentials can range from 900 seconds (15 minutes) up to 3,600 seconds
// range from 900 seconds (15 minutes) up to 3,600 seconds (1 hour), with a // (1 hour), with a default of 1 hour.
// default of 1 hour.
// //
// # Permissions // # Permissions
// //
@@ -1304,20 +1303,20 @@ func (c *STS) GetSessionTokenRequest(input *GetSessionTokenInput) (req *request.
// //
// - You cannot call any STS API except AssumeRole or GetCallerIdentity. // - You cannot call any STS API except AssumeRole or GetCallerIdentity.
// //
// We recommend that you do not call GetSessionToken with Amazon Web Services // The credentials that GetSessionToken returns are based on permissions associated
// account root user credentials. Instead, follow our best practices (https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#create-iam-users) // with the IAM user whose credentials were used to call the operation. The
// by creating one or more IAM users, giving them the necessary permissions, // temporary credentials have the same permissions as the IAM user.
// and using IAM users for everyday interaction with Amazon Web Services.
// //
// The credentials that are returned by GetSessionToken are based on permissions // Although it is possible to call GetSessionToken using the security credentials
// associated with the user whose credentials were used to call the operation. // of an Amazon Web Services account root user rather than an IAM user, we do
// If GetSessionToken is called using Amazon Web Services account root user // not recommend it. If GetSessionToken is called using root user credentials,
// credentials, the temporary credentials have root user permissions. Similarly, // the temporary credentials have root user permissions. For more information,
// if GetSessionToken is called using the credentials of an IAM user, the temporary // see Safeguard your root user credentials and don't use them for everyday
// credentials have the same permissions as the IAM user. // tasks (https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#lock-away-credentials)
// in the IAM User Guide
// //
// For more information about using GetSessionToken to create temporary credentials, // For more information about using GetSessionToken to create temporary credentials,
// go to Temporary Credentials for Users in Untrusted Environments (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#api_getsessiontoken) // see Temporary Credentials for Users in Untrusted Environments (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#api_getsessiontoken)
// in the IAM User Guide. // in the IAM User Guide.
// //
// Returns awserr.Error for service API and SDK errors. Use runtime type assertions // Returns awserr.Error for service API and SDK errors. Use runtime type assertions
@@ -1899,8 +1898,12 @@ type AssumeRoleWithSAMLInput struct {
// For more information, see Configuring a Relying Party and Adding Claims (https://docs.aws.amazon.com/IAM/latest/UserGuide/create-role-saml-IdP-tasks.html) // For more information, see Configuring a Relying Party and Adding Claims (https://docs.aws.amazon.com/IAM/latest/UserGuide/create-role-saml-IdP-tasks.html)
// in the IAM User Guide. // in the IAM User Guide.
// //
// SAMLAssertion is a sensitive parameter and its value will be
// replaced with "sensitive" in string returned by AssumeRoleWithSAMLInput's
// String and GoString methods.
//
// SAMLAssertion is a required field // SAMLAssertion is a required field
SAMLAssertion *string `min:"4" type:"string" required:"true"` SAMLAssertion *string `min:"4" type:"string" required:"true" sensitive:"true"`
} }
// String returns the string representation. // String returns the string representation.
@@ -2035,7 +2038,7 @@ type AssumeRoleWithSAMLOutput struct {
// IAM. // IAM.
// //
// The combination of NameQualifier and Subject can be used to uniquely identify // The combination of NameQualifier and Subject can be used to uniquely identify
// a federated user. // a user.
// //
// The following pseudocode shows how the hash value is calculated: // The following pseudocode shows how the hash value is calculated:
// //
@@ -2265,8 +2268,12 @@ type AssumeRoleWithWebIdentityInput struct {
// the user who is using your application with a web identity provider before // the user who is using your application with a web identity provider before
// the application makes an AssumeRoleWithWebIdentity call. // the application makes an AssumeRoleWithWebIdentity call.
// //
// WebIdentityToken is a sensitive parameter and its value will be
// replaced with "sensitive" in string returned by AssumeRoleWithWebIdentityInput's
// String and GoString methods.
//
// WebIdentityToken is a required field // WebIdentityToken is a required field
WebIdentityToken *string `min:"4" type:"string" required:"true"` WebIdentityToken *string `min:"4" type:"string" required:"true" sensitive:"true"`
} }
// String returns the string representation. // String returns the string representation.
@@ -2572,8 +2579,12 @@ type Credentials struct {
// The secret access key that can be used to sign requests. // The secret access key that can be used to sign requests.
// //
// SecretAccessKey is a sensitive parameter and its value will be
// replaced with "sensitive" in string returned by Credentials's
// String and GoString methods.
//
// SecretAccessKey is a required field // SecretAccessKey is a required field
SecretAccessKey *string `type:"string" required:"true"` SecretAccessKey *string `type:"string" required:"true" sensitive:"true"`
// The token that users must pass to the service API to use the temporary credentials. // The token that users must pass to the service API to use the temporary credentials.
// //
@@ -2921,10 +2932,9 @@ type GetFederationTokenInput struct {
// The duration, in seconds, that the session should last. Acceptable durations // The duration, in seconds, that the session should last. Acceptable durations
// for federation sessions range from 900 seconds (15 minutes) to 129,600 seconds // for federation sessions range from 900 seconds (15 minutes) to 129,600 seconds
// (36 hours), with 43,200 seconds (12 hours) as the default. Sessions obtained // (36 hours), with 43,200 seconds (12 hours) as the default. Sessions obtained
// using Amazon Web Services account root user credentials are restricted to // using root user credentials are restricted to a maximum of 3,600 seconds
// a maximum of 3,600 seconds (one hour). If the specified duration is longer // (one hour). If the specified duration is longer than one hour, the session
// than one hour, the session obtained by using root user credentials defaults // obtained by using root user credentials defaults to one hour.
// to one hour.
DurationSeconds *int64 `min:"900" type:"integer"` DurationSeconds *int64 `min:"900" type:"integer"`
// The name of the federated user. The name is used as an identifier for the // The name of the federated user. The name is used as an identifier for the
+3 -4
View File
@@ -4,10 +4,9 @@
// requests to AWS Security Token Service. // requests to AWS Security Token Service.
// //
// Security Token Service (STS) enables you to request temporary, limited-privilege // Security Token Service (STS) enables you to request temporary, limited-privilege
// credentials for Identity and Access Management (IAM) users or for users that // credentials for users. This guide provides descriptions of the STS API. For
// you authenticate (federated users). This guide provides descriptions of the // more information about using this service, see Temporary Security Credentials
// STS API. For more information about using this service, see Temporary Security // (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html).
// Credentials (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html).
// //
// See https://docs.aws.amazon.com/goto/WebAPI/sts-2011-06-15 for more information on this service. // See https://docs.aws.amazon.com/goto/WebAPI/sts-2011-06-15 for more information on this service.
// //
-16
View File
@@ -1,16 +0,0 @@
sudo: false
language: go
go:
- 1.13.x
- 1.14.x
- 1.15.x
env:
global:
- GO111MODULE=on
script:
- make test
after_success:
- bash <(curl -s https://codecov.io/bash)
-51
View File
@@ -1,51 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
Types of changes:
- `Added` for new features.
- `Changed` for changes in existing functionality.
- `Deprecated` for soon-to-be removed features.
- `Removed` for now removed features.
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.
## [Unreleased]
### Added
- [#223](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/223) Add go-kit logging middleware - [adrien-f](https://github.com/adrien-f)
## [v1.1.0] - 2019-09-12
### Added
- [#226](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/226) Support for go modules.
- [#221](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/221) logging/zap add support for gRPC LoggerV2 - [kush-patel-hs](https://github.com/kush-patel-hs)
- [#181](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/181) Rate Limit support - [ceshihao](https://github.com/ceshihao)
- [#161](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/161) Retry on server stream call - [lonnblad](https://github.com/lonnblad)
- [#152](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/152) Exponential backoff functions - [polyfloyd](https://github.com/polyfloyd)
- [#147](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/147) Jaeger support for ctxtags extraction - [vporoshok](https://github.com/vporoshok)
- [#184](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/184) ctxTags identifies if the call was sampled
### Deprecated
- [#201](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/201) `golang.org/x/net/context` - [houz42](https://github.com/houz42)
- [#183](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/183) Documentation Generation in favour of <godoc.org>.
### Fixed
- [172](https://github.com/grpc-ecosystem/go-grpc-middleware/pull/172) Passing ctx into retry and recover - [johanbrandhorst](https://github.com/johanbrandhorst)
- Numerious documentation fixes.
## v1.0.0 - 2018-05-08
### Added
- grpc_auth
- grpc_ctxtags
- grpc_zap
- grpc_logrus
- grpc_opentracing
- grpc_retry
- grpc_validator
- grpc_recovery
[Unreleased]: https://github.com/grpc-ecosystem/go-grpc-middleware/compare/v1.1.0...HEAD
[v1.1.0]: https://github.com/grpc-ecosystem/go-grpc-middleware/compare/v1.0.0...v1.1.0
+31 -24
View File
@@ -7,16 +7,23 @@
[![codecov](https://codecov.io/gh/grpc-ecosystem/go-grpc-middleware/branch/master/graph/badge.svg)](https://codecov.io/gh/grpc-ecosystem/go-grpc-middleware) [![codecov](https://codecov.io/gh/grpc-ecosystem/go-grpc-middleware/branch/master/graph/badge.svg)](https://codecov.io/gh/grpc-ecosystem/go-grpc-middleware)
[![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE) [![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
[![quality: production](https://img.shields.io/badge/quality-production-orange.svg)](#status) [![quality: production](https://img.shields.io/badge/quality-production-orange.svg)](#status)
[![Slack](https://img.shields.io/badge/slack-%23grpc--middleware-brightgreen)](https://slack.com/share/IRUQCFC23/9Tm7hxRFVKKNoajQfMOcUiIk/enQtODc4ODI4NTIyMDcxLWM5NDA0ZTE4Njg5YjRjYWZkMTI5MzQwNDY3YzBjMzE1YzdjOGM5ZjI1NDNiM2JmNzI2YjM5ODE5OTRiNTEyOWE) [![Slack](https://img.shields.io/badge/slack-%23grpc--middleware-brightgreen)](https://gophers.slack.com/archives/CNJL30P4P)
[gRPC Go](https://github.com/grpc/grpc-go) Middleware: interceptors, helpers, utilities. [gRPC Go](https://github.com/grpc/grpc-go) Middleware: interceptors, helpers, utilities.
## ⚠️ Status
Version [v2](https://github.com/grpc-ecosystem/go-grpc-middleware/tree/v2) is about to be released, with migration guide, which will replace v1. Try v2 and give us feedback!
Version v1 is currently in deprecation mode, which means only critical and safety bug fixes will be merged.
## Middleware ## Middleware
[gRPC Go](https://github.com/grpc/grpc-go) recently acquired support for [gRPC Go](https://github.com/grpc/grpc-go) recently acquired support for
Interceptors, i.e. [middleware](https://medium.com/@matryer/writing-middleware-in-golang-and-how-go-makes-it-so-much-fun-4375c1246e81#.gv7tdlghs) Interceptors, i.e. [middleware](https://medium.com/@matryer/writing-middleware-in-golang-and-how-go-makes-it-so-much-fun-4375c1246e81#.gv7tdlghs)
that is executed either on the gRPC Server before the request is passed onto the user's application logic, or on the gRPC client around the user call. It is a perfect way to implement that is executed either on the gRPC Server before the request is passed onto the user's application logic, or on the gRPC client around the user call. It is a perfect way to implement
common patterns: auth, logging, message, validation, retries or monitoring. common patterns: auth, logging, message, validation, retries, or monitoring.
These are generic building blocks that make it easy to build multiple microservices easily. These are generic building blocks that make it easy to build multiple microservices easily.
The purpose of this repository is to act as a go-to point for such reusable functionality. It contains The purpose of this repository is to act as a go-to point for such reusable functionality. It contains
@@ -29,57 +36,57 @@ import "github.com/grpc-ecosystem/go-grpc-middleware"
myServer := grpc.NewServer( myServer := grpc.NewServer(
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer( grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
grpc_recovery.StreamServerInterceptor(),
grpc_ctxtags.StreamServerInterceptor(), grpc_ctxtags.StreamServerInterceptor(),
grpc_opentracing.StreamServerInterceptor(), grpc_opentracing.StreamServerInterceptor(),
grpc_prometheus.StreamServerInterceptor, grpc_prometheus.StreamServerInterceptor,
grpc_zap.StreamServerInterceptor(zapLogger), grpc_zap.StreamServerInterceptor(zapLogger),
grpc_auth.StreamServerInterceptor(myAuthFunction), grpc_auth.StreamServerInterceptor(myAuthFunction),
grpc_recovery.StreamServerInterceptor(),
)), )),
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer( grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
grpc_recovery.UnaryServerInterceptor(),
grpc_ctxtags.UnaryServerInterceptor(), grpc_ctxtags.UnaryServerInterceptor(),
grpc_opentracing.UnaryServerInterceptor(), grpc_opentracing.UnaryServerInterceptor(),
grpc_prometheus.UnaryServerInterceptor, grpc_prometheus.UnaryServerInterceptor,
grpc_zap.UnaryServerInterceptor(zapLogger), grpc_zap.UnaryServerInterceptor(zapLogger),
grpc_auth.UnaryServerInterceptor(myAuthFunction), grpc_auth.UnaryServerInterceptor(myAuthFunction),
grpc_recovery.UnaryServerInterceptor(),
)), )),
) )
``` ```
## Interceptors ## Interceptors
*Please send a PR to add new interceptors or middleware to this list* _Please send a PR to add new interceptors or middleware to this list_
#### Auth #### Auth
* [`grpc_auth`](auth) - a customizable (via `AuthFunc`) piece of auth middleware
- [`grpc_auth`](auth) - a customizable (via `AuthFunc`) piece of auth middleware
#### Logging #### Logging
* [`grpc_ctxtags`](tags/) - a library that adds a `Tag` map to context, with data populated from request body
* [`grpc_zap`](logging/zap/) - integration of [zap](https://github.com/uber-go/zap) logging library into gRPC handlers. - [`grpc_ctxtags`](tags/) - a library that adds a `Tag` map to context, with data populated from request body
* [`grpc_logrus`](logging/logrus/) - integration of [logrus](https://github.com/sirupsen/logrus) logging library into gRPC handlers. - [`grpc_zap`](logging/zap/) - integration of [zap](https://github.com/uber-go/zap) logging library into gRPC handlers.
* [`grpc_kit`](logging/kit/) - integration of [go-kit](https://github.com/go-kit/kit/tree/master/log) logging library into gRPC handlers. - [`grpc_logrus`](logging/logrus/) - integration of [logrus](https://github.com/sirupsen/logrus) logging library into gRPC handlers.
* [`grpc_grpc_logsettable`](logging/settable/) - a wrapper around `grpclog.LoggerV2` that allows to replace loggers in runtime (thread-safe). - [`grpc_kit`](logging/kit/) - integration of [go-kit/log](https://github.com/go-kit/log) logging library into gRPC handlers.
- [`grpc_grpc_logsettable`](logging/settable/) - a wrapper around `grpclog.LoggerV2` that allows to replace loggers in runtime (thread-safe).
#### Monitoring #### Monitoring
* [`grpc_prometheus`⚡](https://github.com/grpc-ecosystem/go-grpc-prometheus) - Prometheus client-side and server-side monitoring middleware
* [`otgrpc`⚡](https://github.com/grpc-ecosystem/grpc-opentracing/tree/master/go/otgrpc) - [OpenTracing](http://opentracing.io/) client-side and server-side interceptors - [`grpc_prometheus`⚡](https://github.com/grpc-ecosystem/go-grpc-prometheus) - Prometheus client-side and server-side monitoring middleware
* [`grpc_opentracing`](tracing/opentracing) - [OpenTracing](http://opentracing.io/) client-side and server-side interceptors with support for streaming and handler-returned tags - [`otgrpc`⚡](https://github.com/grpc-ecosystem/grpc-opentracing/tree/master/go/otgrpc) - [OpenTracing](http://opentracing.io/) client-side and server-side interceptors
- [`grpc_opentracing`](tracing/opentracing) - [OpenTracing](http://opentracing.io/) client-side and server-side interceptors with support for streaming and handler-returned tags
- [`otelgrpc`](https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/google.golang.org/grpc/otelgrpc) - [OpenTelemetry](https://opentelemetry.io/) client-side and server-side interceptors
#### Client #### Client
* [`grpc_retry`](retry/) - a generic gRPC response code retry mechanism, client-side middleware
- [`grpc_retry`](retry/) - a generic gRPC response code retry mechanism, client-side middleware
#### Server #### Server
* [`grpc_validator`](validator/) - codegen inbound message validation from `.proto` options
* [`grpc_recovery`](recovery/) - turn panics into gRPC errors
* [`ratelimit`](ratelimit/) - grpc rate limiting by your own limiter
- [`grpc_validator`](validator/) - codegen inbound message validation from `.proto` options
- [`grpc_recovery`](recovery/) - turn panics into gRPC errors
- [`ratelimit`](ratelimit/) - grpc rate limiting by your own limiter
## Status
This code has been running in *production* since May 2016 as the basis of the gRPC micro services stack at [Improbable](https://improbable.io).
Additional tooling will be added, and contributions are welcome.
## License ## License
+89 -43
View File
@@ -16,22 +16,41 @@ import (
// Execution is done in left-to-right order, including passing of context. // Execution is done in left-to-right order, including passing of context.
// For example ChainUnaryServer(one, two, three) will execute one before two before three, and three // For example ChainUnaryServer(one, two, three) will execute one before two before three, and three
// will see context changes of one and two. // will see context changes of one and two.
//
// While this can be useful in some scenarios, it is generally advisable to use google.golang.org/grpc.ChainUnaryInterceptor directly.
func ChainUnaryServer(interceptors ...grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor { func ChainUnaryServer(interceptors ...grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor {
n := len(interceptors) n := len(interceptors)
// Dummy interceptor maintained for backward compatibility to avoid returning nil.
if n == 0 {
return func(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
return handler(ctx, req)
}
}
// The degenerate case, just return the single wrapped interceptor directly.
if n == 1 {
return interceptors[0]
}
// Return a function which satisfies the interceptor interface, and which is
// a closure over the given list of interceptors to be chained.
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
chainer := func(currentInter grpc.UnaryServerInterceptor, currentHandler grpc.UnaryHandler) grpc.UnaryHandler { currHandler := handler
return func(currentCtx context.Context, currentReq interface{}) (interface{}, error) { // Iterate backwards through all interceptors except the first (outermost).
return currentInter(currentCtx, currentReq, info, currentHandler) // Wrap each one in a function which satisfies the handler interface, but
// is also a closure over the `info` and `handler` parameters. Then pass
// each pseudo-handler to the next outer interceptor as the handler to be called.
for i := n - 1; i > 0; i-- {
// Rebind to loop-local vars so they can be closed over.
innerHandler, i := currHandler, i
currHandler = func(currentCtx context.Context, currentReq interface{}) (interface{}, error) {
return interceptors[i](currentCtx, currentReq, info, innerHandler)
} }
} }
// Finally return the result of calling the outermost interceptor with the
chainedHandler := handler // outermost pseudo-handler created above as its handler.
for i := n - 1; i >= 0; i-- { return interceptors[0](ctx, req, info, currHandler)
chainedHandler = chainer(interceptors[i], chainedHandler)
}
return chainedHandler(ctx, req)
} }
} }
@@ -40,22 +59,31 @@ func ChainUnaryServer(interceptors ...grpc.UnaryServerInterceptor) grpc.UnarySer
// Execution is done in left-to-right order, including passing of context. // Execution is done in left-to-right order, including passing of context.
// For example ChainUnaryServer(one, two, three) will execute one before two before three. // For example ChainUnaryServer(one, two, three) will execute one before two before three.
// If you want to pass context between interceptors, use WrapServerStream. // If you want to pass context between interceptors, use WrapServerStream.
//
// While this can be useful in some scenarios, it is generally advisable to use google.golang.org/grpc.ChainStreamInterceptor directly.
func ChainStreamServer(interceptors ...grpc.StreamServerInterceptor) grpc.StreamServerInterceptor { func ChainStreamServer(interceptors ...grpc.StreamServerInterceptor) grpc.StreamServerInterceptor {
n := len(interceptors) n := len(interceptors)
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { // Dummy interceptor maintained for backward compatibility to avoid returning nil.
chainer := func(currentInter grpc.StreamServerInterceptor, currentHandler grpc.StreamHandler) grpc.StreamHandler { if n == 0 {
return func(currentSrv interface{}, currentStream grpc.ServerStream) error { return func(srv interface{}, stream grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
return currentInter(currentSrv, currentStream, info, currentHandler) return handler(srv, stream)
}
}
if n == 1 {
return interceptors[0]
}
return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
currHandler := handler
for i := n - 1; i > 0; i-- {
innerHandler, i := currHandler, i
currHandler = func(currentSrv interface{}, currentStream grpc.ServerStream) error {
return interceptors[i](currentSrv, currentStream, info, innerHandler)
} }
} }
return interceptors[0](srv, stream, info, currHandler)
chainedHandler := handler
for i := n - 1; i >= 0; i-- {
chainedHandler = chainer(interceptors[i], chainedHandler)
}
return chainedHandler(srv, ss)
} }
} }
@@ -66,19 +94,26 @@ func ChainStreamServer(interceptors ...grpc.StreamServerInterceptor) grpc.Stream
func ChainUnaryClient(interceptors ...grpc.UnaryClientInterceptor) grpc.UnaryClientInterceptor { func ChainUnaryClient(interceptors ...grpc.UnaryClientInterceptor) grpc.UnaryClientInterceptor {
n := len(interceptors) n := len(interceptors)
// Dummy interceptor maintained for backward compatibility to avoid returning nil.
if n == 0 {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
return invoker(ctx, method, req, reply, cc, opts...)
}
}
if n == 1 {
return interceptors[0]
}
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
chainer := func(currentInter grpc.UnaryClientInterceptor, currentInvoker grpc.UnaryInvoker) grpc.UnaryInvoker { currInvoker := invoker
return func(currentCtx context.Context, currentMethod string, currentReq, currentRepl interface{}, currentConn *grpc.ClientConn, currentOpts ...grpc.CallOption) error { for i := n - 1; i > 0; i-- {
return currentInter(currentCtx, currentMethod, currentReq, currentRepl, currentConn, currentInvoker, currentOpts...) innerInvoker, i := currInvoker, i
currInvoker = func(currentCtx context.Context, currentMethod string, currentReq, currentRepl interface{}, currentConn *grpc.ClientConn, currentOpts ...grpc.CallOption) error {
return interceptors[i](currentCtx, currentMethod, currentReq, currentRepl, currentConn, innerInvoker, currentOpts...)
} }
} }
return interceptors[0](ctx, method, req, reply, cc, currInvoker, opts...)
chainedInvoker := invoker
for i := n - 1; i >= 0; i-- {
chainedInvoker = chainer(interceptors[i], chainedInvoker)
}
return chainedInvoker(ctx, method, req, reply, cc, opts...)
} }
} }
@@ -89,19 +124,26 @@ func ChainUnaryClient(interceptors ...grpc.UnaryClientInterceptor) grpc.UnaryCli
func ChainStreamClient(interceptors ...grpc.StreamClientInterceptor) grpc.StreamClientInterceptor { func ChainStreamClient(interceptors ...grpc.StreamClientInterceptor) grpc.StreamClientInterceptor {
n := len(interceptors) n := len(interceptors)
// Dummy interceptor maintained for backward compatibility to avoid returning nil.
if n == 0 {
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
return streamer(ctx, desc, cc, method, opts...)
}
}
if n == 1 {
return interceptors[0]
}
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
chainer := func(currentInter grpc.StreamClientInterceptor, currentStreamer grpc.Streamer) grpc.Streamer { currStreamer := streamer
return func(currentCtx context.Context, currentDesc *grpc.StreamDesc, currentConn *grpc.ClientConn, currentMethod string, currentOpts ...grpc.CallOption) (grpc.ClientStream, error) { for i := n - 1; i > 0; i-- {
return currentInter(currentCtx, currentDesc, currentConn, currentMethod, currentStreamer, currentOpts...) innerStreamer, i := currStreamer, i
currStreamer = func(currentCtx context.Context, currentDesc *grpc.StreamDesc, currentConn *grpc.ClientConn, currentMethod string, currentOpts ...grpc.CallOption) (grpc.ClientStream, error) {
return interceptors[i](currentCtx, currentDesc, currentConn, currentMethod, innerStreamer, currentOpts...)
} }
} }
return interceptors[0](ctx, desc, cc, method, currStreamer, opts...)
chainedStreamer := streamer
for i := n - 1; i >= 0; i-- {
chainedStreamer = chainer(interceptors[i], chainedStreamer)
}
return chainedStreamer(ctx, desc, cc, method, opts...)
} }
} }
@@ -109,12 +151,16 @@ func ChainStreamClient(interceptors ...grpc.StreamClientInterceptor) grpc.Stream
// //
// WithUnaryServerChain is a grpc.Server config option that accepts multiple unary interceptors. // WithUnaryServerChain is a grpc.Server config option that accepts multiple unary interceptors.
// Basically syntactic sugar. // Basically syntactic sugar.
//
// Deprecated: use google.golang.org/grpc.ChainUnaryInterceptor instead.
func WithUnaryServerChain(interceptors ...grpc.UnaryServerInterceptor) grpc.ServerOption { func WithUnaryServerChain(interceptors ...grpc.UnaryServerInterceptor) grpc.ServerOption {
return grpc.UnaryInterceptor(ChainUnaryServer(interceptors...)) return grpc.ChainUnaryInterceptor(interceptors...)
} }
// WithStreamServerChain is a grpc.Server config option that accepts multiple stream interceptors. // WithStreamServerChain is a grpc.Server config option that accepts multiple stream interceptors.
// Basically syntactic sugar. // Basically syntactic sugar.
//
// Deprecated: use google.golang.org/grpc.ChainStreamInterceptor instead.
func WithStreamServerChain(interceptors ...grpc.StreamServerInterceptor) grpc.ServerOption { func WithStreamServerChain(interceptors ...grpc.StreamServerInterceptor) grpc.ServerOption {
return grpc.StreamInterceptor(ChainStreamServer(interceptors...)) return grpc.ChainStreamInterceptor(interceptors...)
} }
+7
View File
@@ -22,12 +22,19 @@ type Config struct {
// absolute URL containing a scheme, e.g. "http://tus.io" // absolute URL containing a scheme, e.g. "http://tus.io"
BasePath string BasePath string
isAbs bool isAbs bool
// EnableExperimentalProtocol controls whether the new resumable upload protocol draft
// from the IETF's HTTP working group is accepted next to the current tus v1 protocol.
// See https://datatracker.ietf.org/doc/draft-ietf-httpbis-resumable-upload/
EnableExperimentalProtocol bool
// DisableDownload indicates whether the server will refuse downloads of the // DisableDownload indicates whether the server will refuse downloads of the
// uploaded file, by not mounting the GET handler. // uploaded file, by not mounting the GET handler.
DisableDownload bool DisableDownload bool
// DisableTermination indicates whether the server will refuse termination // DisableTermination indicates whether the server will refuse termination
// requests of the uploaded file, by not mounting the DELETE handler. // requests of the uploaded file, by not mounting the DELETE handler.
DisableTermination bool DisableTermination bool
// Disable cors headers. If set to true, tusd will not send any CORS related header.
// This is useful if you have a proxy sitting in front of tusd that handles CORS.
DisableCors bool
// NotifyCompleteUploads indicates whether sending notifications about // NotifyCompleteUploads indicates whether sending notifications about
// completed uploads using the CompleteUploads channel should be enabled. // completed uploads using the CompleteUploads channel should be enabled.
NotifyCompleteUploads bool NotifyCompleteUploads bool
+241 -35
View File
@@ -7,6 +7,7 @@ import (
"io" "io"
"log" "log"
"math" "math"
"mime"
"net" "net"
"net/http" "net/http"
"regexp" "regexp"
@@ -16,6 +17,7 @@ import (
) )
const UploadLengthDeferred = "1" const UploadLengthDeferred = "1"
const currentUploadDraftInteropVersion = "3"
var ( var (
reExtractFileID = regexp.MustCompile(`([^/]+)\/?$`) reExtractFileID = regexp.MustCompile(`([^/]+)\/?$`)
@@ -98,6 +100,12 @@ type HookEvent struct {
} }
func newHookEvent(info FileInfo, r *http.Request) HookEvent { func newHookEvent(info FileInfo, r *http.Request) HookEvent {
// The Host header field is not present in the header map, see https://pkg.go.dev/net/http#Request:
// > For incoming requests, the Host header is promoted to the
// > Request.Host field and removed from the Header map.
// That's why we add it back manually.
r.Header.Set("Host", r.Host)
return HookEvent{ return HookEvent{
Upload: info, Upload: info,
HTTPRequest: HTTPRequest{ HTTPRequest: HTTPRequest{
@@ -217,7 +225,7 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
header := w.Header() header := w.Header()
if origin := r.Header.Get("Origin"); origin != "" { if origin := r.Header.Get("Origin"); !handler.config.DisableCors && origin != "" {
header.Set("Access-Control-Allow-Origin", origin) header.Set("Access-Control-Allow-Origin", origin)
if r.Method == "OPTIONS" { if r.Method == "OPTIONS" {
@@ -232,17 +240,22 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
// Preflight request // Preflight request
header.Add("Access-Control-Allow-Methods", allowedMethods) header.Add("Access-Control-Allow-Methods", allowedMethods)
header.Add("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat") header.Add("Access-Control-Allow-Headers", "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete, Upload-Draft-Interop-Version")
header.Set("Access-Control-Max-Age", "86400") header.Set("Access-Control-Max-Age", "86400")
} else { } else {
// Actual request // Actual request
header.Add("Access-Control-Expose-Headers", "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat") header.Add("Access-Control-Expose-Headers", "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat, Upload-Incomplete, Upload-Draft-Interop-Version")
} }
} }
// Set current version used by the server // Detect requests with tus v1 protocol vs the IETF resumable upload draft
header.Set("Tus-Resumable", "1.0.0") isTusV1 := !handler.isResumableUploadDraftRequest(r)
if isTusV1 {
// Set current version used by the server
header.Set("Tus-Resumable", "1.0.0")
}
// Add nosniff to all responses https://golang.org/src/net/http/server.go#L1429 // Add nosniff to all responses https://golang.org/src/net/http/server.go#L1429
header.Set("X-Content-Type-Options", "nosniff") header.Set("X-Content-Type-Options", "nosniff")
@@ -271,7 +284,7 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
// Test if the version sent by the client is supported // Test if the version sent by the client is supported
// GET and HEAD methods are not checked since a browser may visit this URL and does // GET and HEAD methods are not checked since a browser may visit this URL and does
// not include this header. GET requests are not part of the specification. // not include this header. GET requests are not part of the specification.
if r.Method != "GET" && r.Method != "HEAD" && r.Header.Get("Tus-Resumable") != "1.0.0" { if r.Method != "GET" && r.Method != "HEAD" && r.Header.Get("Tus-Resumable") != "1.0.0" && isTusV1 {
handler.sendError(w, r, ErrUnsupportedVersion) handler.sendError(w, r, ErrUnsupportedVersion)
return return
} }
@@ -284,6 +297,11 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
// PostFile creates a new file upload using the datastore after validating the // PostFile creates a new file upload using the datastore after validating the
// length and parsing the metadata. // length and parsing the metadata.
func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) { func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) {
if handler.isResumableUploadDraftRequest(r) {
handler.PostFileV2(w, r)
return
}
ctx := context.Background() ctx := context.Background()
// Check for presence of application/offset+octet-stream. If another content // Check for presence of application/offset+octet-stream. If another content
@@ -425,6 +443,140 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request)
handler.sendResp(w, r, http.StatusCreated) handler.sendResp(w, r, http.StatusCreated)
} }
// PostFile creates a new file upload using the datastore after validating the
// length and parsing the metadata.
func (handler *UnroutedHandler) PostFileV2(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
// Parse headers
contentType := r.Header.Get("Content-Type")
contentDisposition := r.Header.Get("Content-Disposition")
isComplete := r.Header.Get("Upload-Incomplete") == "?0"
info := FileInfo{
MetaData: make(MetaData),
}
if isComplete && r.ContentLength != -1 {
// If the client wants to perform the upload in one request with Content-Length, we know the final upload size.
info.Size = r.ContentLength
} else {
// Error out if the storage does not support upload length deferring, but we need it.
if !handler.composer.UsesLengthDeferrer {
handler.sendError(w, r, ErrNotImplemented)
return
}
info.SizeIsDeferred = true
}
// Parse Content-Type and Content-Disposition to get file type or file name
if contentType != "" {
fileType, _, err := mime.ParseMediaType(contentType)
if err != nil {
handler.sendError(w, r, err)
return
}
info.MetaData["filetype"] = fileType
}
if contentDisposition != "" {
_, values, err := mime.ParseMediaType(contentDisposition)
if err != nil {
handler.sendError(w, r, err)
return
}
if values["filename"] != "" {
info.MetaData["filename"] = values["filename"]
}
}
// 1. Create upload resource
if handler.config.PreUploadCreateCallback != nil {
if err := handler.config.PreUploadCreateCallback(newHookEvent(info, r)); err != nil {
handler.sendError(w, r, err)
return
}
}
upload, err := handler.composer.Core.NewUpload(ctx, info)
if err != nil {
handler.sendError(w, r, err)
return
}
info, err = upload.GetInfo(ctx)
if err != nil {
handler.sendError(w, r, err)
return
}
id := info.ID
// Add the Location header directly after creating the new resource to even
// include it in cases of failure when an error is returned
url := handler.absFileURL(r, id)
w.Header().Set("Location", url)
// Send 104 response
w.Header().Set("Upload-Draft-Interop-Version", currentUploadDraftInteropVersion)
w.WriteHeader(104)
handler.Metrics.incUploadsCreated()
handler.log("UploadCreated", "id", id, "size", i64toa(info.Size), "url", url)
if handler.config.NotifyCreatedUploads {
handler.CreatedUploads <- newHookEvent(info, r)
}
// 2. Lock upload
if handler.composer.UsesLocker {
lock, err := handler.lockUpload(id)
if err != nil {
handler.sendError(w, r, err)
return
}
defer lock.Unlock()
}
// 3. Write chunk
if err := handler.writeChunk(ctx, upload, info, w, r); err != nil {
handler.sendError(w, r, err)
return
}
// 4. Finish upload, if necessary
if isComplete && info.SizeIsDeferred {
info, err = upload.GetInfo(ctx)
if err != nil {
handler.sendError(w, r, err)
return
}
uploadLength := info.Offset
lengthDeclarableUpload := handler.composer.LengthDeferrer.AsLengthDeclarableUpload(upload)
if err := lengthDeclarableUpload.DeclareLength(ctx, uploadLength); err != nil {
handler.sendError(w, r, err)
return
}
info.Size = uploadLength
info.SizeIsDeferred = false
if err := handler.finishUploadIfComplete(ctx, upload, info, r); err != nil {
handler.sendError(w, r, err)
return
}
}
handler.sendResp(w, r, http.StatusCreated)
}
// HeadFile returns the length and offset for the HEAD request // HeadFile returns the length and offset for the HEAD request
func (handler *UnroutedHandler) HeadFile(w http.ResponseWriter, r *http.Request) { func (handler *UnroutedHandler) HeadFile(w http.ResponseWriter, r *http.Request) {
ctx := context.Background() ctx := context.Background()
@@ -457,36 +609,52 @@ func (handler *UnroutedHandler) HeadFile(w http.ResponseWriter, r *http.Request)
return return
} }
// Add Upload-Concat header if possible
if info.IsPartial {
w.Header().Set("Upload-Concat", "partial")
}
if info.IsFinal {
v := "final;"
for _, uploadID := range info.PartialUploads {
v += handler.absFileURL(r, uploadID) + " "
}
// Remove trailing space
v = v[:len(v)-1]
w.Header().Set("Upload-Concat", v)
}
if len(info.MetaData) != 0 {
w.Header().Set("Upload-Metadata", SerializeMetadataHeader(info.MetaData))
}
if info.SizeIsDeferred {
w.Header().Set("Upload-Defer-Length", UploadLengthDeferred)
} else {
w.Header().Set("Upload-Length", strconv.FormatInt(info.Size, 10))
w.Header().Set("Content-Length", strconv.FormatInt(info.Size, 10))
}
w.Header().Set("Cache-Control", "no-store") w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Upload-Offset", strconv.FormatInt(info.Offset, 10)) w.Header().Set("Upload-Offset", strconv.FormatInt(info.Offset, 10))
handler.sendResp(w, r, http.StatusOK)
if !handler.isResumableUploadDraftRequest(r) {
// Add Upload-Concat header if possible
if info.IsPartial {
w.Header().Set("Upload-Concat", "partial")
}
if info.IsFinal {
v := "final;"
for _, uploadID := range info.PartialUploads {
v += handler.absFileURL(r, uploadID) + " "
}
// Remove trailing space
v = v[:len(v)-1]
w.Header().Set("Upload-Concat", v)
}
if len(info.MetaData) != 0 {
w.Header().Set("Upload-Metadata", SerializeMetadataHeader(info.MetaData))
}
if info.SizeIsDeferred {
w.Header().Set("Upload-Defer-Length", UploadLengthDeferred)
} else {
w.Header().Set("Upload-Length", strconv.FormatInt(info.Size, 10))
w.Header().Set("Content-Length", strconv.FormatInt(info.Size, 10))
}
// TODO: We send a 200 OK here by default. Can we switch this to 204?
handler.sendResp(w, r, http.StatusOK)
} else {
if !info.SizeIsDeferred && info.Offset == info.Size {
// Upload is complete if we know the size and it matches the offset.
w.Header().Set("Upload-Incomplete", "?0")
} else {
w.Header().Set("Upload-Incomplete", "?1")
}
w.Header().Set("Upload-Draft-Interop-Version", currentUploadDraftInteropVersion)
// Draft requires a 204 No Content response
handler.sendResp(w, r, http.StatusNoContent)
}
} }
// PatchFile adds a chunk to an upload. This operation is only allowed // PatchFile adds a chunk to an upload. This operation is only allowed
@@ -494,8 +662,10 @@ func (handler *UnroutedHandler) HeadFile(w http.ResponseWriter, r *http.Request)
func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request) { func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request) {
ctx := context.Background() ctx := context.Background()
isTusV1 := !handler.isResumableUploadDraftRequest(r)
// Check for presence of application/offset+octet-stream // Check for presence of application/offset+octet-stream
if r.Header.Get("Content-Type") != "application/offset+octet-stream" { if isTusV1 && r.Header.Get("Content-Type") != "application/offset+octet-stream" {
handler.sendError(w, r, ErrInvalidContentType) handler.sendError(w, r, ErrInvalidContentType)
return return
} }
@@ -546,6 +716,10 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request
return return
} }
// TODO: If Upload-Incomplete: ?0 and Content-Length is set, we can
// - declare the length already here
// - validate that the length from this request matches info.Size if !info.SizeIsDeferred
// Do not proxy the call to the data store if the upload is already completed // Do not proxy the call to the data store if the upload is already completed
if !info.SizeIsDeferred && info.Offset == info.Size { if !info.SizeIsDeferred && info.Offset == info.Size {
w.Header().Set("Upload-Offset", strconv.FormatInt(offset, 10)) w.Header().Set("Upload-Offset", strconv.FormatInt(offset, 10))
@@ -583,6 +757,32 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request
return return
} }
isComplete := r.Header.Get("Upload-Incomplete") == "?0"
if isComplete && info.SizeIsDeferred {
info, err = upload.GetInfo(ctx)
if err != nil {
handler.sendError(w, r, err)
return
}
uploadLength := info.Offset
lengthDeclarableUpload := handler.composer.LengthDeferrer.AsLengthDeclarableUpload(upload)
if err := lengthDeclarableUpload.DeclareLength(ctx, uploadLength); err != nil {
handler.sendError(w, r, err)
return
}
info.Size = uploadLength
info.SizeIsDeferred = false
if err := handler.finishUploadIfComplete(ctx, upload, info, r); err != nil {
handler.sendError(w, r, err)
return
}
}
handler.sendResp(w, r, http.StatusNoContent) handler.sendResp(w, r, http.StatusNoContent)
} }
@@ -1130,6 +1330,12 @@ func (handler *UnroutedHandler) lockUpload(id string) (Lock, error) {
return lock, nil return lock, nil
} }
// isResumableUploadDraftRequest returns whether a HTTP request includes a sign that it is
// related to resumable upload draft from IETF (instead of tus v1)
func (handler UnroutedHandler) isResumableUploadDraftRequest(r *http.Request) bool {
return handler.config.EnableExperimentalProtocol && r.Header.Get("Upload-Draft-Interop-Version") == currentUploadDraftInteropVersion
}
// ParseMetadataHeader parses the Upload-Metadata header as defined in the // ParseMetadataHeader parses the Upload-Metadata header as defined in the
// File Creation extension. // File Creation extension.
// e.g. Upload-Metadata: name bHVucmpzLnBuZw==,type aW1hZ2UvcG5n // e.g. Upload-Metadata: name bHVucmpzLnBuZw==,type aW1hZ2UvcG5n
+5 -5
View File
@@ -20,7 +20,7 @@ type token struct{}
// A zero Group is valid, has no limit on the number of active goroutines, // A zero Group is valid, has no limit on the number of active goroutines,
// and does not cancel on error. // and does not cancel on error.
type Group struct { type Group struct {
cancel func() cancel func(error)
wg sync.WaitGroup wg sync.WaitGroup
@@ -43,7 +43,7 @@ func (g *Group) done() {
// returns a non-nil error or the first time Wait returns, whichever occurs // returns a non-nil error or the first time Wait returns, whichever occurs
// first. // first.
func WithContext(ctx context.Context) (*Group, context.Context) { func WithContext(ctx context.Context) (*Group, context.Context) {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := withCancelCause(ctx)
return &Group{cancel: cancel}, ctx return &Group{cancel: cancel}, ctx
} }
@@ -52,7 +52,7 @@ func WithContext(ctx context.Context) (*Group, context.Context) {
func (g *Group) Wait() error { func (g *Group) Wait() error {
g.wg.Wait() g.wg.Wait()
if g.cancel != nil { if g.cancel != nil {
g.cancel() g.cancel(g.err)
} }
return g.err return g.err
} }
@@ -76,7 +76,7 @@ func (g *Group) Go(f func() error) {
g.errOnce.Do(func() { g.errOnce.Do(func() {
g.err = err g.err = err
if g.cancel != nil { if g.cancel != nil {
g.cancel() g.cancel(g.err)
} }
}) })
} }
@@ -105,7 +105,7 @@ func (g *Group) TryGo(f func() error) bool {
g.errOnce.Do(func() { g.errOnce.Do(func() {
g.err = err g.err = err
if g.cancel != nil { if g.cancel != nil {
g.cancel() g.cancel(g.err)
} }
}) })
} }
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.20
// +build go1.20
package errgroup
import "context"
func withCancelCause(parent context.Context) (context.Context, func(error)) {
return context.WithCancelCause(parent)
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.20
// +build !go1.20
package errgroup
import "context"
func withCancelCause(parent context.Context) (context.Context, func(error)) {
ctx, cancel := context.WithCancel(parent)
return ctx, func(error) { cancel() }
}
+6 -6
View File
@@ -87,7 +87,7 @@ github.com/armon/go-radix
# github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d # github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
## explicit; go 1.13 ## explicit; go 1.13
github.com/asaskevich/govalidator github.com/asaskevich/govalidator
# github.com/aws/aws-sdk-go v1.44.181 # github.com/aws/aws-sdk-go v1.44.294
## explicit; go 1.11 ## explicit; go 1.11
github.com/aws/aws-sdk-go/aws github.com/aws/aws-sdk-go/aws
github.com/aws/aws-sdk-go/aws/arn github.com/aws/aws-sdk-go/aws/arn
@@ -1101,7 +1101,7 @@ github.com/gorilla/mux
# github.com/gorilla/schema v1.2.0 # github.com/gorilla/schema v1.2.0
## explicit ## explicit
github.com/gorilla/schema github.com/gorilla/schema
# github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 # github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
## explicit; go 1.14 ## explicit; go 1.14
github.com/grpc-ecosystem/go-grpc-middleware github.com/grpc-ecosystem/go-grpc-middleware
github.com/grpc-ecosystem/go-grpc-middleware/recovery github.com/grpc-ecosystem/go-grpc-middleware/recovery
@@ -1671,7 +1671,7 @@ github.com/trustelem/zxcvbn/internal/mathutils
github.com/trustelem/zxcvbn/match github.com/trustelem/zxcvbn/match
github.com/trustelem/zxcvbn/matching github.com/trustelem/zxcvbn/matching
github.com/trustelem/zxcvbn/scoring github.com/trustelem/zxcvbn/scoring
# github.com/tus/tusd v1.10.1 # github.com/tus/tusd v1.12.1
## explicit; go 1.16 ## explicit; go 1.16
github.com/tus/tusd/pkg/handler github.com/tus/tusd/pkg/handler
# github.com/urfave/cli/v2 v2.25.7 # github.com/urfave/cli/v2 v2.25.7
@@ -1979,8 +1979,8 @@ golang.org/x/net/trace
## explicit; go 1.17 ## explicit; go 1.17
golang.org/x/oauth2 golang.org/x/oauth2
golang.org/x/oauth2/internal golang.org/x/oauth2/internal
# golang.org/x/sync v0.2.0 # golang.org/x/sync v0.3.0
## explicit ## explicit; go 1.17
golang.org/x/sync/errgroup golang.org/x/sync/errgroup
golang.org/x/sync/semaphore golang.org/x/sync/semaphore
golang.org/x/sync/singleflight golang.org/x/sync/singleflight
@@ -2059,7 +2059,7 @@ google.golang.org/appengine/internal/log
google.golang.org/appengine/internal/remote_api google.golang.org/appengine/internal/remote_api
google.golang.org/appengine/internal/urlfetch google.golang.org/appengine/internal/urlfetch
google.golang.org/appengine/urlfetch google.golang.org/appengine/urlfetch
# google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e # google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc
## explicit; go 1.19 ## explicit; go 1.19
google.golang.org/genproto/internal google.golang.org/genproto/internal
google.golang.org/genproto/protobuf/field_mask google.golang.org/genproto/protobuf/field_mask