Skip to content

Commit 039a779

Browse files
authored
Merge pull request #4507 from butonic/fix-tus-cors
bump tusd and make CORS configurable
2 parents 6a7ec89 + e5ed64e commit 039a779

File tree

3 files changed

+87
-12
lines changed

3 files changed

+87
-12
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Bugfix: make tusd CORS headers configurable
2+
3+
We bumped tusd to 1.13.0 and made CORS headers configurable via mapstructure.
4+
5+
https://github.com/cs3org/reva/pull/4507

internal/http/services/datagateway/datagateway.go

+49-8
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ func (s *svc) setHandler() {
135135
r = r.WithContext(ctx)
136136
switch r.Method {
137137
case "HEAD":
138-
addCorsHeader(w)
139138
s.doHead(w, r)
140139
return
141140
case "GET":
@@ -147,20 +146,16 @@ func (s *svc) setHandler() {
147146
case "PATCH":
148147
s.doPatch(w, r)
149148
return
149+
case "OPTIONS":
150+
s.doOptions(w, r)
151+
return
150152
default:
151153
w.WriteHeader(http.StatusNotImplemented)
152154
return
153155
}
154156
})
155157
}
156158

157-
func addCorsHeader(res http.ResponseWriter) {
158-
headers := res.Header()
159-
headers.Set("Access-Control-Allow-Origin", "*")
160-
headers.Set("Access-Control-Allow-Headers", "Content-Type, Origin, Authorization")
161-
headers.Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD")
162-
}
163-
164159
func (s *svc) verify(ctx context.Context, r *http.Request) (*transferClaims, error) {
165160
// Extract transfer token from request header. If not existing, assume that it's the last path segment instead.
166161
token := r.Header.Get(TokenTransportHeader)
@@ -408,6 +403,52 @@ func (s *svc) doPatch(w http.ResponseWriter, r *http.Request) {
408403
}
409404
}
410405

406+
func (s *svc) doOptions(w http.ResponseWriter, r *http.Request) {
407+
ctx := r.Context()
408+
log := appctx.GetLogger(ctx)
409+
410+
claims, err := s.verify(ctx, r)
411+
if err != nil {
412+
err = errors.Wrap(err, "datagateway: error validating transfer token")
413+
log.Error().Err(err).Str("token", r.Header.Get(TokenTransportHeader)).Msg("invalid transfer token")
414+
w.WriteHeader(http.StatusForbidden)
415+
return
416+
}
417+
418+
log.Debug().Str("target", claims.Target).Msg("sending request to internal data server")
419+
420+
httpClient := s.client
421+
httpReq, err := rhttp.NewRequest(ctx, "OPTIONS", claims.Target, nil)
422+
if err != nil {
423+
log.Error().Err(err).Msg("wrong request")
424+
w.WriteHeader(http.StatusInternalServerError)
425+
return
426+
}
427+
httpReq.Header = r.Header
428+
429+
httpRes, err := httpClient.Do(httpReq)
430+
if err != nil {
431+
log.Error().Err(err).Msg("error doing OPTIONS request to data service")
432+
w.WriteHeader(http.StatusInternalServerError)
433+
return
434+
}
435+
defer httpRes.Body.Close()
436+
437+
copyHeader(w.Header(), httpRes.Header)
438+
439+
// add upload expiry / transfer token expiry header for tus https://tus.io/protocols/resumable-upload.html#expiration
440+
w.Header().Set(UploadExpiresHeader, time.Unix(claims.ExpiresAt, 0).Format(time.RFC1123))
441+
442+
if httpRes.StatusCode != http.StatusOK {
443+
// swallow the body and set content-length to 0 to prevent reverse proxies from trying to read from it
444+
w.Header().Set("Content-Length", "0")
445+
w.WriteHeader(httpRes.StatusCode)
446+
return
447+
}
448+
449+
w.WriteHeader(http.StatusOK)
450+
}
451+
411452
func copyHeader(dst, src http.Header) {
412453
for key, values := range src {
413454
for i := range values {

pkg/rhttp/datatx/manager/tus/tus.go

+33-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"log"
2424
"net/http"
2525
"path"
26+
"regexp"
2627

2728
"github.com/pkg/errors"
2829
tusd "github.com/tus/tusd/pkg/handler"
@@ -45,14 +46,25 @@ func init() {
4546
registry.Register("tus", New)
4647
}
4748

49+
type TusConfig struct {
50+
cache.Config
51+
CorsEnabled bool `mapstructure:"cors_enabled"`
52+
CorsAllowOrigin string `mapstructure:"cors_allow_origin"`
53+
CorsAllowCredentials bool `mapstructure:"cors_allow_credentials"`
54+
CorsAllowMethods string `mapstructure:"cors_allow_methods"`
55+
CorsAllowHeaders string `mapstructure:"cors_allow_headers"`
56+
CorsMaxAge string `mapstructure:"cors_max_age"`
57+
CorsExposeHeaders string `mapstructure:"cors_expose_headers"`
58+
}
59+
4860
type manager struct {
49-
conf *cache.Config
61+
conf *TusConfig
5062
publisher events.Publisher
5163
statCache cache.StatCache
5264
}
5365

54-
func parseConfig(m map[string]interface{}) (*cache.Config, error) {
55-
c := &cache.Config{}
66+
func parseConfig(m map[string]interface{}) (*TusConfig, error) {
67+
c := &TusConfig{}
5668
if err := mapstructure.Decode(m, c); err != nil {
5769
err = errors.Wrap(err, "error decoding conf")
5870
return nil, err
@@ -69,7 +81,7 @@ func New(m map[string]interface{}, publisher events.Publisher) (datatx.DataTX, e
6981
return &manager{
7082
conf: c,
7183
publisher: publisher,
72-
statCache: cache.GetStatCache(*c),
84+
statCache: cache.GetStatCache(c.Config),
7385
}, nil
7486
}
7587

@@ -94,6 +106,23 @@ func (m *manager) Handler(fs storage.FS) (http.Handler, error) {
94106
Logger: log.New(appctx.GetLogger(context.Background()), "", 0),
95107
}
96108

109+
if m.conf.CorsEnabled {
110+
allowOrigin, err := regexp.Compile(m.conf.CorsAllowOrigin)
111+
if m.conf.CorsAllowOrigin != "" && err != nil {
112+
return nil, err
113+
}
114+
115+
config.Cors = &tusd.CorsConfig{
116+
Disable: false,
117+
AllowOrigin: allowOrigin,
118+
AllowCredentials: m.conf.CorsAllowCredentials,
119+
AllowMethods: m.conf.CorsAllowMethods,
120+
AllowHeaders: m.conf.CorsAllowHeaders,
121+
MaxAge: m.conf.CorsMaxAge,
122+
ExposeHeaders: m.conf.CorsExposeHeaders,
123+
}
124+
}
125+
97126
handler, err := tusd.NewUnroutedHandler(config)
98127
if err != nil {
99128
return nil, err

0 commit comments

Comments
 (0)