Bump reva

This commit is contained in:
André Duffeck
2025-07-16 10:29:34 +02:00
parent e21fe7a4fe
commit 63ab8f789e
49 changed files with 13621 additions and 847 deletions
@@ -33,10 +33,11 @@ type Config struct {
ProductVersion string `mapstructure:"product_version"`
AllowPropfindDepthInfinitiy bool `mapstructure:"allow_depth_infinity"`
TransferSharedSecret string `mapstructure:"transfer_shared_secret"`
NameValidation NameValidation `mapstructure:"validation"`
// SharedSecret used to sign the 'oc:download' URLs
URLSigningSharedSecret string `mapstructure:"url_signing_shared_secret"`
MachineAuthAPIKey string `mapstructure:"machine_auth_apikey"`
}
@@ -20,6 +20,7 @@ package ocdav
import (
"context"
"fmt"
"io"
"net/http"
"path"
@@ -41,6 +42,7 @@ import (
"github.com/opencloud-eu/reva/v2/pkg/rhttp"
"github.com/opencloud-eu/reva/v2/pkg/rhttp/global"
"github.com/opencloud-eu/reva/v2/pkg/rhttp/router"
"github.com/opencloud-eu/reva/v2/pkg/signedurl"
"github.com/opencloud-eu/reva/v2/pkg/storage/favorite"
"github.com/opencloud-eu/reva/v2/pkg/storage/favorite/registry"
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/templates"
@@ -69,6 +71,7 @@ type svc struct {
LockSystem LockSystem
userIdentifierCache *ttlcache.Cache
nameValidators []Validator
urlSigner signedurl.Signer
}
func (s *svc) Config() *config.Config {
@@ -116,6 +119,15 @@ func NewWith(conf *config.Config, fm favorite.Manager, ls LockSystem, _ *zerolog
// be safe - init the conf again
conf.Init()
var signer signedurl.Signer
if conf.URLSigningSharedSecret != "" {
var err error
signer, err = signedurl.NewJWTSignedURL(signedurl.WithSecret(conf.URLSigningSharedSecret))
if err != nil {
return nil, fmt.Errorf("failed to initialize URL signer: %w", err)
}
}
s := &svc{
c: conf,
webDavHandler: new(WebDavHandler),
@@ -129,6 +141,7 @@ func NewWith(conf *config.Config, fm favorite.Manager, ls LockSystem, _ *zerolog
LockSystem: ls,
userIdentifierCache: ttlcache.NewCache(),
nameValidators: ValidatorsFromConfig(conf),
urlSigner: signer,
}
_ = s.userIdentifierCache.SetTTL(60 * time.Second)
@@ -52,6 +52,7 @@ import (
rstatus "github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/opencloud-eu/reva/v2/pkg/rhttp/router"
"github.com/opencloud-eu/reva/v2/pkg/signedurl"
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"github.com/rs/zerolog"
@@ -214,14 +215,16 @@ type Handler struct {
PublicURL string
selector pool.Selectable[gateway.GatewayAPIClient]
c *config.Config
urlSigner signedurl.Signer
}
// NewHandler returns a new PropfindHandler instance
func NewHandler(publicURL string, selector pool.Selectable[gateway.GatewayAPIClient], c *config.Config) *Handler {
func NewHandler(publicURL string, selector pool.Selectable[gateway.GatewayAPIClient], signer signedurl.Signer, c *config.Config) *Handler {
return &Handler{
PublicURL: publicURL,
selector: selector,
c: c,
urlSigner: signer,
}
}
@@ -494,7 +497,7 @@ func (p *Handler) propfindResponse(ctx context.Context, w http.ResponseWriter, r
prefer := net.ParsePrefer(r.Header.Get(net.HeaderPrefer))
returnMinimal := prefer[net.HeaderPreferReturn] == "minimal"
propRes, err := MultistatusResponse(ctx, &pf, resourceInfos, p.PublicURL, namespace, linkshares, returnMinimal)
propRes, err := MultistatusResponse(ctx, &pf, resourceInfos, p.PublicURL, namespace, linkshares, returnMinimal, p.urlSigner)
if err != nil {
log.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
@@ -985,7 +988,7 @@ func ReadPropfind(r io.Reader) (pf XML, status int, err error) {
}
// MultistatusResponse converts a list of resource infos into a multistatus response string
func MultistatusResponse(ctx context.Context, pf *XML, mds []*provider.ResourceInfo, publicURL, ns string, linkshares map[string]struct{}, returnMinimal bool) ([]byte, error) {
func MultistatusResponse(ctx context.Context, pf *XML, mds []*provider.ResourceInfo, publicURL, ns string, linkshares map[string]struct{}, returnMinimal bool, downloadURLSigner signedurl.Signer) ([]byte, error) {
g, ctx := errgroup.WithContext(ctx)
type work struct {
@@ -1020,7 +1023,7 @@ func MultistatusResponse(ctx context.Context, pf *XML, mds []*provider.ResourceI
for i := 0; i < numWorkers; i++ {
g.Go(func() error {
for work := range workChan {
res, err := mdToPropResponse(ctx, pf, work.info, publicURL, ns, linkshares, returnMinimal)
res, err := mdToPropResponse(ctx, pf, work.info, publicURL, ns, linkshares, returnMinimal, downloadURLSigner)
if err != nil {
return err
}
@@ -1061,7 +1064,7 @@ func MultistatusResponse(ctx context.Context, pf *XML, mds []*provider.ResourceI
// mdToPropResponse converts the CS3 metadata into a webdav PropResponse
// ns is the CS3 namespace that needs to be removed from the CS3 path before
// prefixing it with the baseURI
func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, publicURL, ns string, linkshares map[string]struct{}, returnMinimal bool) (*ResponseXML, error) {
func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, publicURL, ns string, linkshares map[string]struct{}, returnMinimal bool, urlSigner signedurl.Signer) (*ResponseXML, error) {
ctx, span := appctx.GetTracerProvider(ctx).Tracer(tracerName).Start(ctx, "md_to_prop_response")
span.SetAttributes(attribute.KeyValue{Key: "publicURL", Value: attribute.StringValue(publicURL)})
span.SetAttributes(attribute.KeyValue{Key: "ns", Value: attribute.StringValue(ns)})
@@ -1516,23 +1519,14 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p
appendToNotFound(prop.NotFound("oc:owner-display-name"))
}
case "downloadURL": // desktop
if isPublic && md.Type == provider.ResourceType_RESOURCE_TYPE_FILE {
var path string
if !ls.PasswordProtected {
path = p
if md.Type == provider.ResourceType_RESOURCE_TYPE_FILE {
url := downloadURL(ctx, sublog, isPublic, p, ls, publicURL, baseURI, urlSigner)
if url != "" {
appendToOK(prop.Escaped("oc:downloadURL", url))
} else {
expiration := time.Unix(int64(ls.Signature.SignatureExpiration.Seconds), int64(ls.Signature.SignatureExpiration.Nanos))
var sb strings.Builder
sb.WriteString(p)
sb.WriteString("?signature=")
sb.WriteString(ls.Signature.Signature)
sb.WriteString("&expiration=")
sb.WriteString(url.QueryEscape(expiration.Format(time.RFC3339)))
path = sb.String()
appendToNotFound(prop.NotFound("oc:" + pf.Prop[i].Local))
}
appendToOK(prop.Escaped("oc:downloadURL", publicURL+baseURI+path))
} else {
appendToNotFound(prop.NotFound("oc:" + pf.Prop[i].Local))
}
@@ -1738,6 +1732,42 @@ func hasPreview(md *provider.ResourceInfo, appendToOK func(p ...prop.PropertyXML
}
}
func downloadURL(ctx context.Context, log zerolog.Logger, isPublic bool, path string, ls *link.PublicShare, publicURL string, baseURI string, urlSigner signedurl.Signer) string {
switch {
case isPublic:
var queryString string
if !ls.PasswordProtected {
queryString = path
} else {
expiration := time.Unix(int64(ls.Signature.SignatureExpiration.Seconds), int64(ls.Signature.SignatureExpiration.Nanos))
var sb strings.Builder
sb.WriteString(path)
sb.WriteString("?signature=")
sb.WriteString(ls.Signature.Signature)
sb.WriteString("&expiration=")
sb.WriteString(url.QueryEscape(expiration.Format(time.RFC3339)))
queryString = sb.String()
}
return publicURL + baseURI + queryString
case urlSigner != nil:
u, ok := ctxpkg.ContextGetUser(ctx)
if !ok {
log.Error().Msg("could not get user from context for download URL signing")
return ""
}
signedURL, err := urlSigner.Sign(publicURL+baseURI+path, u.Id.OpaqueId, 30*time.Minute)
if err != nil {
log.Error().Err(err).Msg("failed to sign download URL")
return ""
} else {
return signedURL
}
}
return ""
}
func activeLocks(log *zerolog.Logger, lock *provider.Lock) string {
if lock == nil || lock.Type == provider.LockType_LOCK_TYPE_INVALID {
return ""
@@ -147,7 +147,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s
prefer := net.ParsePrefer(r.Header.Get("prefer"))
returnMinimal := prefer[net.HeaderPreferReturn] == "minimal"
propRes, err := propfind.MultistatusResponse(ctx, &pf, infos, s.c.PublicURL, ns, nil, returnMinimal)
propRes, err := propfind.MultistatusResponse(ctx, &pf, infos, s.c.PublicURL, ns, nil, returnMinimal, nil)
if err != nil {
sublog.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
@@ -117,7 +117,7 @@ func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFi
prefer := net.ParsePrefer(r.Header.Get("prefer"))
returnMinimal := prefer[net.HeaderPreferReturn] == "minimal"
responsesXML, err := propfind.MultistatusResponse(ctx, &propfind.XML{Prop: ff.Prop}, infos, s.c.PublicURL, namespace, nil, returnMinimal)
responsesXML, err := propfind.MultistatusResponse(ctx, &propfind.XML{Prop: ff.Prop}, infos, s.c.PublicURL, namespace, nil, returnMinimal, nil)
if err != nil {
log.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
@@ -82,7 +82,7 @@ func (h *SpacesHandler) Handler(s *svc, trashbinHandler *TrashbinHandler) http.H
var err error
switch r.Method {
case MethodPropfind:
p := propfind.NewHandler(config.PublicURL, s.gatewaySelector, config)
p := propfind.NewHandler(config.PublicURL, s.gatewaySelector, s.urlSigner, config)
p.HandleSpacesPropfind(w, r, spaceID)
case MethodProppatch:
status, err = s.handleSpacesProppatch(w, r, spaceID)
@@ -200,7 +200,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request,
prefer := net.ParsePrefer(r.Header.Get("prefer"))
returnMinimal := prefer[net.HeaderPreferReturn] == "minimal"
propRes, err := propfind.MultistatusResponse(ctx, &pf, infos, s.c.PublicURL, "", nil, returnMinimal)
propRes, err := propfind.MultistatusResponse(ctx, &pf, infos, s.c.PublicURL, "", nil, returnMinimal, nil)
if err != nil {
sublog.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
@@ -72,7 +72,7 @@ func (h *WebDavHandler) Handler(s *svc) http.Handler {
var status int // status 0 means the handler already sent the response
switch r.Method {
case MethodPropfind:
p := propfind.NewHandler(config.PublicURL, s.gatewaySelector, config)
p := propfind.NewHandler(config.PublicURL, s.gatewaySelector, s.urlSigner, config)
p.HandlePathPropfind(w, r, ns)
case MethodLock:
status, err = s.handleLock(w, r, ns)