Bump reva

This commit is contained in:
André Duffeck
2025-02-13 10:08:22 +01:00
parent 5b85029813
commit 52e61d46d1
208 changed files with 11004 additions and 5254 deletions
@@ -1,620 +0,0 @@
// Copyright 2018-2022 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 cs3
import (
"context"
"encoding/json"
"fmt"
"net/url"
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"golang.org/x/crypto/bcrypt"
"google.golang.org/protobuf/proto"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/appctx"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/publicshare"
"github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/registry"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/indexer"
indexerErrors "github.com/opencloud-eu/reva/v2/pkg/storage/utils/indexer/errors"
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/indexer/option"
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/metadata"
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
"github.com/opencloud-eu/reva/v2/pkg/utils"
)
func init() {
registry.Register("cs3", NewDefault)
}
// Manager implements a publicshare manager using a cs3 storage backend
type Manager struct {
gatewayClient gateway.GatewayAPIClient
sync.RWMutex
storage metadata.Storage
indexer indexer.Indexer
passwordHashCost int
initialized bool
}
type config struct {
GatewayAddr string `mapstructure:"gateway_addr"`
ProviderAddr string `mapstructure:"provider_addr"`
ServiceUserID string `mapstructure:"service_user_id"`
ServiceUserIdp string `mapstructure:"service_user_idp"`
MachineAuthAPIKey string `mapstructure:"machine_auth_apikey"`
}
// NewDefault returns a new manager instance with default dependencies
func NewDefault(m map[string]interface{}) (publicshare.Manager, error) {
c := &config{}
if err := mapstructure.Decode(m, c); err != nil {
err = errors.Wrap(err, "error creating a new manager")
return nil, err
}
s, err := metadata.NewCS3Storage(c.GatewayAddr, c.ProviderAddr, c.ServiceUserID, c.ServiceUserIdp, c.MachineAuthAPIKey)
if err != nil {
return nil, err
}
indexer := indexer.CreateIndexer(s)
client, err := pool.GetGatewayServiceClient(c.GatewayAddr)
if err != nil {
return nil, err
}
return New(client, s, indexer, bcrypt.DefaultCost)
}
// New returns a new manager instance
func New(gatewayClient gateway.GatewayAPIClient, storage metadata.Storage, indexer indexer.Indexer, passwordHashCost int) (*Manager, error) {
return &Manager{
gatewayClient: gatewayClient,
storage: storage,
indexer: indexer,
passwordHashCost: passwordHashCost,
initialized: false,
}, nil
}
func (m *Manager) initialize() error {
if m.initialized {
return nil
}
m.Lock()
defer m.Unlock()
if m.initialized { // check if initialization happened while grabbing the lock
return nil
}
err := m.storage.Init(context.Background(), "public-share-manager-metadata")
if err != nil {
return err
}
if err := m.storage.MakeDirIfNotExist(context.Background(), "publicshares"); err != nil {
return err
}
err = m.indexer.AddIndex(&link.PublicShare{}, option.IndexByField("Id.OpaqueId"), "Token", "publicshares", "unique", nil, true)
if err != nil {
return err
}
err = m.indexer.AddIndex(&link.PublicShare{}, option.IndexByFunc{
Name: "Owner",
Func: indexOwnerFunc,
}, "Token", "publicshares", "non_unique", nil, true)
if err != nil {
return err
}
err = m.indexer.AddIndex(&link.PublicShare{}, option.IndexByFunc{
Name: "Creator",
Func: indexCreatorFunc,
}, "Token", "publicshares", "non_unique", nil, true)
if err != nil {
return err
}
err = m.indexer.AddIndex(&link.PublicShare{}, option.IndexByFunc{
Name: "ResourceId",
Func: indexResourceIDFunc,
}, "Token", "publicshares", "non_unique", nil, true)
if err != nil {
return err
}
m.initialized = true
return nil
}
// Dump exports public shares to channels (e.g. during migration)
func (m *Manager) Dump(ctx context.Context, shareChan chan<- *publicshare.WithPassword) error {
if err := m.initialize(); err != nil {
return err
}
pshares, err := m.storage.ListDir(ctx, "publicshares")
if err != nil {
return err
}
for _, v := range pshares {
var local publicshare.WithPassword
ps, err := m.getByToken(ctx, v.Name)
if err != nil {
return err
}
local.Password = ps.Password
proto.Merge(&local.PublicShare, &ps.PublicShare)
shareChan <- &local
}
return nil
}
// Load imports public shares and received shares from channels (e.g. during migration)
func (m *Manager) Load(ctx context.Context, shareChan <-chan *publicshare.WithPassword) error {
log := appctx.GetLogger(ctx)
if err := m.initialize(); err != nil {
return err
}
for ps := range shareChan {
if err := m.persist(context.Background(), ps); err != nil {
log.Error().Err(err).Interface("publicshare", ps).Msg("error loading public share")
}
}
return nil
}
// CreatePublicShare creates a new public share
func (m *Manager) CreatePublicShare(ctx context.Context, u *user.User, ri *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error) {
if err := m.initialize(); err != nil {
return nil, err
}
id := &link.PublicShareId{
OpaqueId: utils.RandString(15),
}
tkn := utils.RandString(15)
now := time.Now().UnixNano()
displayName, quicklink := tkn, false
if ri.ArbitraryMetadata != nil {
metadataName, ok := ri.ArbitraryMetadata.Metadata["name"]
if ok {
displayName = metadataName
}
quicklink, _ = strconv.ParseBool(ri.ArbitraryMetadata.Metadata["quicklink"])
}
var passwordProtected bool
password := g.Password
if password != "" {
h, err := bcrypt.GenerateFromPassword([]byte(password), m.passwordHashCost)
if err != nil {
return nil, errors.Wrap(err, "could not hash share password")
}
password = string(h)
passwordProtected = true
}
createdAt := &typespb.Timestamp{
Seconds: uint64(now / int64(time.Second)),
Nanos: uint32(now % int64(time.Second)),
}
s := &publicshare.WithPassword{
PublicShare: link.PublicShare{
Id: id,
Owner: ri.GetOwner(),
Creator: u.Id,
ResourceId: ri.Id,
Token: tkn,
Permissions: g.Permissions,
Ctime: createdAt,
Mtime: createdAt,
PasswordProtected: passwordProtected,
Expiration: g.Expiration,
DisplayName: displayName,
Quicklink: quicklink,
},
Password: password,
}
err := m.persist(ctx, s)
if err != nil {
return nil, err
}
return &s.PublicShare, nil
}
// UpdatePublicShare updates an existing public share
func (m *Manager) UpdatePublicShare(ctx context.Context, u *user.User, req *link.UpdatePublicShareRequest) (*link.PublicShare, error) {
if err := m.initialize(); err != nil {
return nil, err
}
ps, err := m.getWithPassword(ctx, req.Ref)
if err != nil {
return nil, err
}
switch req.Update.Type {
case link.UpdatePublicShareRequest_Update_TYPE_DISPLAYNAME:
ps.PublicShare.DisplayName = req.Update.DisplayName
case link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS:
ps.PublicShare.Permissions = req.Update.Grant.Permissions
case link.UpdatePublicShareRequest_Update_TYPE_EXPIRATION:
ps.PublicShare.Expiration = req.Update.Grant.Expiration
case link.UpdatePublicShareRequest_Update_TYPE_PASSWORD:
if req.Update.Grant.Password == "" {
ps.Password = ""
ps.PublicShare.PasswordProtected = false
} else {
h, err := bcrypt.GenerateFromPassword([]byte(req.Update.Grant.Password), m.passwordHashCost)
if err != nil {
return nil, errors.Wrap(err, "could not hash share password")
}
ps.Password = string(h)
ps.PublicShare.PasswordProtected = true
}
default:
return nil, errtypes.BadRequest("no valid update type given")
}
err = m.persist(ctx, ps)
if err != nil {
return nil, err
}
return &ps.PublicShare, nil
}
// GetPublicShare returns an existing public share
func (m *Manager) GetPublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference, sign bool) (*link.PublicShare, error) {
if err := m.initialize(); err != nil {
return nil, err
}
ps, err := m.getWithPassword(ctx, ref)
if err != nil {
return nil, err
}
if ps.PublicShare.PasswordProtected && sign {
err = publicshare.AddSignature(&ps.PublicShare, ps.Password)
if err != nil {
return nil, err
}
}
return &ps.PublicShare, nil
}
func (m *Manager) getWithPassword(ctx context.Context, ref *link.PublicShareReference) (*publicshare.WithPassword, error) {
switch {
case ref.GetToken() != "":
return m.getByToken(ctx, ref.GetToken())
case ref.GetId().GetOpaqueId() != "":
return m.getByID(ctx, ref.GetId().GetOpaqueId())
default:
return nil, errtypes.BadRequest("neither id nor token given")
}
}
func (m *Manager) getByID(ctx context.Context, id string) (*publicshare.WithPassword, error) {
tokens, err := m.indexer.FindBy(&link.PublicShare{},
indexer.NewField("Id.OpaqueId", id),
)
if err != nil {
return nil, err
}
if len(tokens) == 0 {
return nil, errtypes.NotFound("publicshare with the given id not found")
}
return m.getByToken(ctx, tokens[0])
}
func (m *Manager) getByToken(ctx context.Context, token string) (*publicshare.WithPassword, error) {
fn := path.Join("publicshares", token)
data, err := m.storage.SimpleDownload(ctx, fn)
if err != nil {
return nil, err
}
ps := &publicshare.WithPassword{}
err = json.Unmarshal(data, ps)
if err != nil {
return nil, err
}
id := storagespace.UpdateLegacyResourceID(ps.PublicShare.ResourceId)
ps.PublicShare.ResourceId = id
return ps, nil
}
// ListPublicShares lists existing public shares matching the given filters
func (m *Manager) ListPublicShares(ctx context.Context, u *user.User, filters []*link.ListPublicSharesRequest_Filter, sign bool) ([]*link.PublicShare, error) {
if err := m.initialize(); err != nil {
return nil, err
}
log := appctx.GetLogger(ctx)
var rIDs []*provider.ResourceId
if len(filters) != 0 {
grouped := publicshare.GroupFiltersByType(filters)
for _, g := range grouped {
for _, f := range g {
if f.GetResourceId() != nil {
rIDs = append(rIDs, f.GetResourceId())
}
}
}
}
var (
createdShareTokens []string
err error
)
// in spaces, always use the resourceId
if len(rIDs) != 0 {
for _, rID := range rIDs {
shareTokens, err := m.indexer.FindBy(&link.PublicShare{},
indexer.NewField("ResourceId", resourceIDToIndex(rID)),
)
if err != nil {
return nil, err
}
createdShareTokens = append(createdShareTokens, shareTokens...)
}
} else {
// fallback for legacy use
createdShareTokens, err = m.indexer.FindBy(&link.PublicShare{},
indexer.NewField("Owner", userIDToIndex(u.Id)),
indexer.NewField("Creator", userIDToIndex(u.Id)),
)
if err != nil {
return nil, err
}
}
// We use shareMem as a temporary lookup store to check which shares were
// already added. This is to prevent duplicates.
shareMem := make(map[string]struct{})
result := []*link.PublicShare{}
for _, token := range createdShareTokens {
ps, err := m.getByToken(ctx, token)
if err != nil {
return nil, err
}
if !publicshare.MatchesFilters(&ps.PublicShare, filters) {
continue
}
if publicshare.IsExpired(&ps.PublicShare) {
ref := &link.PublicShareReference{
Spec: &link.PublicShareReference_Id{
Id: ps.PublicShare.Id,
},
}
if err := m.RevokePublicShare(ctx, u, ref); err != nil {
log.Error().Err(err).
Str("public_share_token", ps.PublicShare.Token).
Str("public_share_id", ps.PublicShare.Id.OpaqueId).
Msg("failed to revoke expired public share")
}
continue
}
if ps.PublicShare.PasswordProtected && sign {
err = publicshare.AddSignature(&ps.PublicShare, ps.Password)
if err != nil {
return nil, err
}
}
result = append(result, &ps.PublicShare)
shareMem[ps.PublicShare.Token] = struct{}{}
}
// If a user requests to list shares which have not been created by them
// we have to explicitly fetch these shares and check if the user is
// allowed to list the shares.
// Only then can we add these shares to the result.
grouped := publicshare.GroupFiltersByType(filters)
idFilter, ok := grouped[link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID]
if !ok {
return result, nil
}
var tokens []string
if len(idFilter) > 0 {
idFilters := make([]indexer.Field, 0, len(idFilter))
for _, filter := range idFilter {
resourceID := filter.GetResourceId()
idFilters = append(idFilters, indexer.NewField("ResourceId", resourceIDToIndex(resourceID)))
}
tokens, err = m.indexer.FindBy(&link.PublicShare{}, idFilters...)
if err != nil {
return nil, err
}
}
// statMem is used as a local cache to prevent statting resources which
// already have been checked.
statMem := make(map[string]struct{})
for _, token := range tokens {
if _, handled := shareMem[token]; handled {
// We don't want to add a share multiple times when we added it
// already.
continue
}
s, err := m.getByToken(ctx, token)
if err != nil {
return nil, err
}
if _, checked := statMem[resourceIDToIndex(s.PublicShare.GetResourceId())]; !checked {
sReq := &provider.StatRequest{
Ref: &provider.Reference{ResourceId: s.PublicShare.GetResourceId()},
}
sRes, err := m.gatewayClient.Stat(ctx, sReq)
if err != nil {
continue
}
if sRes.Status.Code != rpc.Code_CODE_OK {
continue
}
if !sRes.Info.PermissionSet.ListGrants {
continue
}
statMem[resourceIDToIndex(s.PublicShare.GetResourceId())] = struct{}{}
}
if publicshare.MatchesFilters(&s.PublicShare, filters) {
result = append(result, &s.PublicShare)
shareMem[s.PublicShare.Token] = struct{}{}
}
}
return result, nil
}
// RevokePublicShare revokes an existing public share
func (m *Manager) RevokePublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference) error {
if err := m.initialize(); err != nil {
return err
}
ps, err := m.GetPublicShare(ctx, u, ref, false)
if err != nil {
return err
}
err = m.storage.Delete(ctx, path.Join("publicshares", ps.Token))
if err != nil {
if _, ok := err.(errtypes.NotFound); !ok {
return err
}
}
return m.indexer.Delete(ps)
}
// GetPublicShareByToken gets an existing public share in an unauthenticated context using either a password or a signature
func (m *Manager) GetPublicShareByToken(ctx context.Context, token string, auth *link.PublicShareAuthentication, sign bool) (*link.PublicShare, error) {
if err := m.initialize(); err != nil {
return nil, err
}
ps, err := m.getByToken(ctx, token)
if err != nil {
return nil, err
}
if publicshare.IsExpired(&ps.PublicShare) {
return nil, errtypes.NotFound("public share has expired")
}
if ps.PublicShare.PasswordProtected {
if !publicshare.Authenticate(&ps.PublicShare, ps.Password, auth) {
return nil, errtypes.InvalidCredentials("access denied")
}
}
return &ps.PublicShare, nil
}
func indexOwnerFunc(v interface{}) (string, error) {
ps, ok := v.(*link.PublicShare)
if !ok {
return "", fmt.Errorf("given entity is not a public share")
}
return userIDToIndex(ps.Owner), nil
}
func indexCreatorFunc(v interface{}) (string, error) {
ps, ok := v.(*link.PublicShare)
if !ok {
return "", fmt.Errorf("given entity is not a public share")
}
return userIDToIndex(ps.Creator), nil
}
func indexResourceIDFunc(v interface{}) (string, error) {
ps, ok := v.(*link.PublicShare)
if !ok {
return "", fmt.Errorf("given entity is not a public share")
}
return resourceIDToIndex(ps.ResourceId), nil
}
func userIDToIndex(id *user.UserId) string {
return url.QueryEscape(id.Idp + ":" + id.OpaqueId)
}
func resourceIDToIndex(id *provider.ResourceId) string {
return strings.Join([]string{id.StorageId, id.OpaqueId}, "!")
}
func (m *Manager) persist(ctx context.Context, ps *publicshare.WithPassword) error {
data, err := json.Marshal(ps)
if err != nil {
return err
}
fn := path.Join("publicshares", ps.PublicShare.Token)
err = m.storage.SimpleUpload(ctx, fn, data)
if err != nil {
return err
}
_, err = m.indexer.Add(&ps.PublicShare)
if err != nil {
if _, ok := err.(*indexerErrors.AlreadyExistsErr); ok {
return nil
}
err = m.indexer.Delete(&ps.PublicShare)
if err != nil {
return err
}
_, err = m.indexer.Add(&ps.PublicShare)
return err
}
return nil
}
@@ -20,7 +20,6 @@ package loader
import (
// Load core share manager drivers.
_ "github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/cs3"
_ "github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/json"
_ "github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/memory"
_ "github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/owncloudsql"