reva-bump-2.42.2

This commit is contained in:
Viktor Scharf
2026-02-05 10:51:11 +01:00
committed by Ralf Haferkamp
parent f21207ed96
commit 2bf70a6f70
102 changed files with 20909 additions and 1542 deletions

View File

@@ -75,11 +75,6 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s
return nil
}
case strings.HasPrefix(k, "share"):
if err = resolveUserShare(ctx, ref, tokenScope[k], client, mgr); err == nil {
return nil
}
case strings.HasPrefix(k, "lightweight"):
if err = resolveLightweightScope(ctx, ref, tokenScope[k], user, client, mgr); err == nil {
return nil
@@ -130,8 +125,13 @@ func expandAndVerifyScope(ctx context.Context, req interface{}, tokenScope map[s
}
func resolveLightweightScope(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, user *userpb.User, client gateway.GatewayAPIClient, mgr token.Manager) error {
refString, err := storagespace.FormatReference(ref)
if err != nil {
// cannot format reference, so cannot be valid
return errtypes.PermissionDenied("invalid reference")
}
// Check if this ref is cached
key := "lw:" + user.Id.OpaqueId + scopeDelimiter + getRefKey(ref)
key := "lw:" + user.Id.OpaqueId + scopeDelimiter + refString
if _, err := scopeExpansionCache.Get(key); err == nil {
return nil
}
@@ -164,13 +164,7 @@ func resolvePublicShare(ctx context.Context, ref *provider.Reference, scope *aut
return err
}
if err := checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil {
return nil
}
// Some services like wopi don't access the shared resource relative to the
// share root but instead relative to the shared resources parent.
return checkRelativeReference(ctx, ref, share.ResourceId, client)
return checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr)
}
func resolveOCMShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error {
@@ -184,69 +178,18 @@ func resolveOCMShare(ctx context.Context, ref *provider.Reference, scope *authpb
ref.ResourceId = share.GetResourceId()
}
if err := checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr); err == nil {
return nil
}
// Some services like wopi don't access the shared resource relative to the
// share root but instead relative to the shared resources parent.
return checkRelativeReference(ctx, ref, share.ResourceId, client)
}
// checkRelativeReference checks if the shared resource is being accessed via a relative reference
// e.g.:
// storage: abcd, space: efgh
// /root (id: efgh)
// - New file.txt (id: ijkl) <- shared resource
//
// If the requested reference looks like this:
// Reference{ResourceId: {StorageId: "abcd", SpaceId: "efgh"}, Path: "./New file.txt"}
// then the request is considered relative and this function would return true.
// Only references which are relative to the immediate parent of a resource are considered valid.
func checkRelativeReference(ctx context.Context, requested *provider.Reference, sharedResourceID *provider.ResourceId, client gateway.GatewayAPIClient) error {
sRes, err := client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: sharedResourceID}})
if err != nil {
return err
}
if sRes.Status.Code != rpc.Code_CODE_OK {
return statuspkg.NewErrorFromCode(sRes.Status.Code, "auth interceptor")
}
sharedResource := sRes.Info
// Is this a shared space
if sharedResource.ParentId == nil {
// Is the requested resource part of the shared space?
if requested.ResourceId.StorageId != sharedResource.Id.StorageId || requested.ResourceId.SpaceId != sharedResource.Id.SpaceId {
return errtypes.PermissionDenied("space access forbidden via public link")
}
} else {
parentID := sharedResource.ParentId
parentID.StorageId = sharedResource.Id.StorageId
if !utils.ResourceIDEqual(parentID, requested.ResourceId) && utils.MakeRelativePath(sharedResource.Path) != requested.Path {
return errtypes.PermissionDenied("access forbidden via public link")
}
}
key := storagespace.FormatResourceID(sharedResourceID) + scopeDelimiter + getRefKey(requested)
_ = scopeExpansionCache.SetWithExpire(key, nil, scopeCacheExpiration*time.Second)
return nil
}
func resolveUserShare(ctx context.Context, ref *provider.Reference, scope *authpb.Scope, client gateway.GatewayAPIClient, mgr token.Manager) error {
var share collaboration.Share
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
if err != nil {
return err
}
return checkCacheForNestedResource(ctx, ref, share.ResourceId, client, mgr)
}
func checkCacheForNestedResource(ctx context.Context, ref *provider.Reference, resource *provider.ResourceId, client gateway.GatewayAPIClient, mgr token.Manager) error {
refString, err := storagespace.FormatReference(ref)
if err != nil {
// cannot format reference, so cannot be valid
return errtypes.PermissionDenied("invalid reference")
}
// Check if this ref is cached
key := storagespace.FormatResourceID(resource) + scopeDelimiter + getRefKey(ref)
key := storagespace.FormatResourceID(resource) + scopeDelimiter + refString
if _, err := scopeExpansionCache.Get(key); err == nil {
return nil
}
@@ -270,40 +213,25 @@ func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent
return false, statuspkg.NewErrorFromCode(statResponse.Status.Code, "auth interceptor")
}
pathResp, err := client.GetPath(ctx, &provider.GetPathRequest{ResourceId: statResponse.GetInfo().GetId()})
if err != nil {
return false, err
}
if pathResp.Status.Code != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(pathResp.Status.Code, "auth interceptor")
}
parentPath := pathResp.Path
parentInfo := statResponse.GetInfo()
childPath := ref.GetPath()
if childPath != "" && childPath != "." && strings.HasPrefix(childPath, parentPath) {
// if the request is relative from the root, we can return directly
return true, nil
}
// The request is not relative to the root. We need to find out if the requested resource is child of the `parent` (coming from token scope)
// We need to find out if the requested resource is child of the `parent` (coming from token scope)
// We mint a token as the owner of the public share and try to stat the reference
// TODO(ishank011): We need to find a better alternative to this
// NOTE: did somebody say service accounts? ...
var user *userpb.User
if statResponse.GetInfo().GetOwner().GetType() == userpb.UserType_USER_TYPE_SPACE_OWNER {
if parentInfo.GetOwner().GetType() == userpb.UserType_USER_TYPE_SPACE_OWNER {
// fake a space owner user
user = &userpb.User{
Id: statResponse.GetInfo().GetOwner(),
Id: parentInfo.GetOwner(),
}
} else {
userResp, err := client.GetUser(ctx, &userpb.GetUserRequest{UserId: statResponse.Info.Owner, SkipFetchingUserGroups: true})
userResp, err := client.GetUser(ctx, &userpb.GetUserRequest{UserId: parentInfo.GetOwner(), SkipFetchingUserGroups: true})
if err != nil || userResp.Status.Code != rpc.Code_CODE_OK {
return false, err
}
user = userResp.User
}
scope, err := scope.AddOwnerScope(map[string]*authpb.Scope{})
if err != nil {
return false, err
@@ -329,6 +257,24 @@ func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent
if childStat.GetStatus().GetCode() != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(childStat.Status.Code, "auth interceptor")
}
childInfo := childStat.GetInfo()
// child can only be a nested resource if it is within the same space as parent
if childInfo.GetId().GetStorageId() != parentInfo.GetId().GetStorageId() ||
childInfo.GetId().GetSpaceId() != parentInfo.GetId().GetSpaceId() {
return false, nil
}
// Both resources are in the same space, now check paths
pathResp, err := client.GetPath(ctx, &provider.GetPathRequest{ResourceId: statResponse.GetInfo().GetId()})
if err != nil {
return false, err
}
if pathResp.Status.Code != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(pathResp.Status.Code, "auth interceptor")
}
parentPath := pathResp.Path
pathResp, err = client.GetPath(ctx, &provider.GetPathRequest{ResourceId: childStat.GetInfo().GetId()})
if err != nil {
return false, err
@@ -336,10 +282,9 @@ func checkIfNestedResource(ctx context.Context, ref *provider.Reference, parent
if pathResp.GetStatus().GetCode() != rpc.Code_CODE_OK {
return false, statuspkg.NewErrorFromCode(pathResp.Status.Code, "auth interceptor")
}
childPath = pathResp.Path
childPath := pathResp.Path
return strings.HasPrefix(childPath, parentPath), nil
}
func extractRefFromListProvidersReq(v *registry.ListStorageProvidersRequest) (*provider.Reference, bool) {
@@ -513,17 +458,3 @@ func extractShareRef(req interface{}) (*collaboration.ShareReference, bool) {
}
return nil, false
}
func getRefKey(ref *provider.Reference) string {
if ref.GetPath() != "" {
return ref.Path
}
if ref.GetResourceId() != nil {
return storagespace.FormatResourceID(ref.ResourceId)
}
// on malicious request both path and rid could be empty
// we still should not panic
return ""
}

View File

@@ -21,6 +21,7 @@ package ocdav
import (
"context"
"fmt"
"io"
"net/http"
"path"
"path/filepath"
@@ -274,7 +275,7 @@ func (s *svc) executePathCopy(ctx context.Context, selector pool.Selectable[gate
var uploadEP, uploadToken string
for _, p := range uRes.Protocols {
if p.Protocol == "simple" {
if p.Protocol == "tus" {
uploadEP, uploadToken = p.UploadEndpoint, p.Token
}
}
@@ -303,24 +304,10 @@ func (s *svc) executePathCopy(ctx context.Context, selector pool.Selectable[gate
}
// 4. do upload
httpUploadReq, err := rhttp.NewRequest(ctx, "PUT", uploadEP, httpDownloadRes.Body)
fileid, err = s.tusUpload(ctx, uploadEP, uploadToken, httpDownloadRes.Body, int64(cp.sourceInfo.GetSize()))
if err != nil {
return err
}
httpUploadReq.Header.Set(datagateway.TokenTransportHeader, uploadToken)
httpUploadReq.ContentLength = int64(cp.sourceInfo.GetSize())
httpUploadRes, err := s.client.Do(httpUploadReq)
if err != nil {
return err
}
defer httpUploadRes.Body.Close()
if httpUploadRes.StatusCode != http.StatusOK {
return err
}
fileid = httpUploadRes.Header.Get(net.HeaderOCFileID)
}
w.Header().Set(net.HeaderOCFileID, fileid)
@@ -498,7 +485,7 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, sele
var uploadEP, uploadToken string
for _, p := range uRes.Protocols {
if p.Protocol == "simple" {
if p.Protocol == "tus" {
uploadEP, uploadToken = p.UploadEndpoint, p.Token
}
}
@@ -530,24 +517,10 @@ func (s *svc) executeSpacesCopy(ctx context.Context, w http.ResponseWriter, sele
}
// 4. do upload
httpUploadReq, err := rhttp.NewRequest(ctx, http.MethodPut, uploadEP, httpDownloadRes.Body)
fileid, err = s.tusUpload(ctx, uploadEP, uploadToken, httpDownloadRes.Body, int64(cp.sourceInfo.GetSize()))
if err != nil {
return err
}
httpUploadReq.Header.Set(datagateway.TokenTransportHeader, uploadToken)
httpUploadReq.ContentLength = int64(cp.sourceInfo.GetSize())
httpUploadRes, err := s.client.Do(httpUploadReq)
if err != nil {
return err
}
defer httpUploadRes.Body.Close()
if httpUploadRes.StatusCode != http.StatusOK {
return err
}
fileid = httpUploadRes.Header.Get(net.HeaderOCFileID)
}
w.Header().Set(net.HeaderOCFileID, fileid)
@@ -756,3 +729,55 @@ func (s *svc) prepareCopy(ctx context.Context, w http.ResponseWriter, r *http.Re
return &copy{source: srcRef, sourceInfo: srcStatRes.Info, depth: depth, successCode: successCode, destination: dstRef}
}
func (s *svc) tusUpload(ctx context.Context, uploadEP, uploadToken string, body io.Reader, size int64) (string, error) {
chunkSize := int64(10000000)
var offset int64
var fileid string
for offset < size {
n := chunkSize
if offset+n > size {
n = size - offset
}
req, err := rhttp.NewRequest(ctx, http.MethodPatch, uploadEP, io.LimitReader(body, n))
if err != nil {
return "", err
}
req.Header.Set(datagateway.TokenTransportHeader, uploadToken)
req.Header.Set(net.HeaderTusResumable, "1.0.0")
req.Header.Set(net.HeaderUploadOffset, strconv.FormatInt(offset, 10))
req.Header.Set(net.HeaderContentType, "application/offset+octet-stream")
req.ContentLength = n
res, err := s.client.Do(req)
if err != nil {
return "", err
}
if res.StatusCode != http.StatusNoContent && res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated {
res.Body.Close()
return "", fmt.Errorf("unexpected status code during TUS upload: %d", res.StatusCode)
}
if id := res.Header.Get(net.HeaderOCFileID); id != "" {
fileid = id
}
newOffsetStr := res.Header.Get(net.HeaderUploadOffset)
res.Body.Close()
if newOffsetStr != "" {
newOffset, err := strconv.ParseInt(newOffsetStr, 10, 64)
if err != nil {
return "", fmt.Errorf("invalid Upload-Offset header: %v", err)
}
offset = newOffset
} else {
offset += n
}
}
return fileid, nil
}

View File

@@ -1,75 +0,0 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package scope
import (
"context"
"fmt"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"github.com/rs/zerolog"
)
func receivedShareScope(_ context.Context, scope *authpb.Scope, resource interface{}, logger *zerolog.Logger) (bool, error) {
var share collaboration.ReceivedShare
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
if err != nil {
return false, err
}
switch v := resource.(type) {
case *collaboration.GetReceivedShareRequest:
return checkShareRef(share.Share, v.GetRef()), nil
case *collaboration.UpdateReceivedShareRequest:
return checkShare(share.Share, v.GetShare().GetShare()), nil
case string:
return checkSharePath(v) || checkResourcePath(v), nil
}
msg := fmt.Sprintf("resource type assertion failed: %+v", resource)
logger.Debug().Str("scope", "receivedShareScope").Msg(msg)
return false, errtypes.InternalError(msg)
}
// AddReceivedShareScope adds the scope to allow access to a received user/group share and
// the shared resource.
func AddReceivedShareScope(share *collaboration.ReceivedShare, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
// Create a new "scope share" to only expose the required fields to the scope.
scopeShare := &collaboration.Share{Id: share.Share.Id, Owner: share.Share.Owner, Creator: share.Share.Creator, ResourceId: share.Share.ResourceId}
val, err := utils.MarshalProtoV1ToJSON(&collaboration.ReceivedShare{Share: scopeShare})
if err != nil {
return nil, err
}
if scopes == nil {
scopes = make(map[string]*authpb.Scope)
}
scopes["receivedshare:"+share.Share.Id.OpaqueId] = &authpb.Scope{
Resource: &types.OpaqueEntry{
Decoder: "json",
Value: val,
},
Role: role,
}
return scopes, nil
}

View File

@@ -31,13 +31,11 @@ import (
type Verifier func(context.Context, *authpb.Scope, interface{}, *zerolog.Logger) (bool, error)
var supportedScopes = map[string]Verifier{
"user": userScope,
"publicshare": publicshareScope,
"resourceinfo": resourceinfoScope,
"share": shareScope,
"receivedshare": receivedShareScope,
"lightweight": lightweightAccountScope,
"ocmshare": ocmShareScope,
"user": userScope,
"publicshare": publicshareScope,
"resourceinfo": resourceinfoScope,
"lightweight": lightweightAccountScope,
"ocmshare": ocmShareScope,
}
// VerifyScope is the function to be called when dismantling tokens to check if

View File

@@ -1,142 +0,0 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package scope
import (
"context"
"fmt"
"strings"
authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
registry "github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/utils"
"github.com/rs/zerolog"
)
func shareScope(_ context.Context, scope *authpb.Scope, resource interface{}, logger *zerolog.Logger) (bool, error) {
var share collaboration.Share
err := utils.UnmarshalJSONToProtoV1(scope.Resource.Value, &share)
if err != nil {
return false, err
}
switch v := resource.(type) {
// Viewer role
case *registry.GetStorageProvidersRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *provider.StatRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *provider.ListContainerRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *provider.InitiateFileDownloadRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
// Editor role
// TODO(ishank011): Add role checks,
// need to return appropriate status codes in the ocs/ocdav layers.
case *provider.CreateContainerRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *provider.TouchFileRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *provider.DeleteRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *provider.MoveRequest:
return checkShareStorageRef(&share, v.GetSource()) && checkShareStorageRef(&share, v.GetDestination()), nil
case *provider.InitiateFileUploadRequest:
return checkShareStorageRef(&share, v.GetRef()), nil
case *collaboration.ListReceivedSharesRequest:
return true, nil
case *collaboration.GetReceivedShareRequest:
return checkShareRef(&share, v.GetRef()), nil
case string:
return checkSharePath(v) || checkResourcePath(v), nil
}
msg := fmt.Sprintf("resource type assertion failed: %+v", resource)
logger.Debug().Str("scope", "shareScope").Msg(msg)
return false, errtypes.InternalError(msg)
}
func checkShareStorageRef(s *collaboration.Share, r *provider.Reference) bool {
// ref: <id:<storage_id:$storageID opaque_id:$opaqueID > >
if r.GetResourceId() != nil && r.Path == "" { // path must be empty
return utils.ResourceIDEqual(s.ResourceId, r.GetResourceId())
}
return false
}
func checkShareRef(s *collaboration.Share, ref *collaboration.ShareReference) bool {
if ref.GetId() != nil {
return ref.GetId().OpaqueId == s.Id.OpaqueId
}
if key := ref.GetKey(); key != nil {
return (utils.UserEqual(key.Owner, s.Owner) || utils.UserEqual(key.Owner, s.Creator)) &&
utils.ResourceIDEqual(key.ResourceId, s.ResourceId) && utils.GranteeEqual(key.Grantee, s.Grantee)
}
return false
}
func checkShare(s1 *collaboration.Share, s2 *collaboration.Share) bool {
if s2.GetId() != nil {
return s2.GetId().OpaqueId == s1.Id.OpaqueId
}
return false
}
func checkSharePath(path string) bool {
paths := []string{
"/ocs/v2.php/apps/files_sharing/api/v1/shares",
"/ocs/v1.php/apps/files_sharing/api/v1/shares",
"/remote.php/webdav",
"/remote.php/dav/files",
}
for _, p := range paths {
if strings.HasPrefix(path, p) {
return true
}
}
return false
}
// AddShareScope adds the scope to allow access to a user/group share and
// the shared resource.
func AddShareScope(share *collaboration.Share, role authpb.Role, scopes map[string]*authpb.Scope) (map[string]*authpb.Scope, error) {
// Create a new "scope share" to only expose the required fields to the scope.
scopeShare := &collaboration.Share{Id: share.Id, Owner: share.Owner, Creator: share.Creator, ResourceId: share.ResourceId}
val, err := utils.MarshalProtoV1ToJSON(scopeShare)
if err != nil {
return nil, err
}
if scopes == nil {
scopes = make(map[string]*authpb.Scope)
}
scopes["share:"+share.Id.OpaqueId] = &authpb.Scope{
Resource: &types.OpaqueEntry{
Decoder: "json",
Value: val,
},
Role: role,
}
return scopes, nil
}