github.com/cs3org/reva/v2@v2.27.7/pkg/publicshare/manager/cs3/cs3.go (about)

     1  // Copyright 2018-2022 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package cs3
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"net/url"
    26  	"path"
    27  	"strconv"
    28  	"strings"
    29  	"sync"
    30  	"time"
    31  
    32  	"github.com/mitchellh/mapstructure"
    33  	"github.com/pkg/errors"
    34  	"golang.org/x/crypto/bcrypt"
    35  	"google.golang.org/protobuf/proto"
    36  
    37  	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
    38  	user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    39  	rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    40  	link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
    41  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    42  	typespb "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    43  	"github.com/cs3org/reva/v2/pkg/appctx"
    44  	"github.com/cs3org/reva/v2/pkg/errtypes"
    45  	"github.com/cs3org/reva/v2/pkg/publicshare"
    46  	"github.com/cs3org/reva/v2/pkg/publicshare/manager/registry"
    47  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    48  	"github.com/cs3org/reva/v2/pkg/storage/utils/indexer"
    49  	indexerErrors "github.com/cs3org/reva/v2/pkg/storage/utils/indexer/errors"
    50  	"github.com/cs3org/reva/v2/pkg/storage/utils/indexer/option"
    51  	"github.com/cs3org/reva/v2/pkg/storage/utils/metadata"
    52  	"github.com/cs3org/reva/v2/pkg/storagespace"
    53  	"github.com/cs3org/reva/v2/pkg/utils"
    54  )
    55  
    56  func init() {
    57  	registry.Register("cs3", NewDefault)
    58  }
    59  
    60  // Manager implements a publicshare manager using a cs3 storage backend
    61  type Manager struct {
    62  	gatewayClient gateway.GatewayAPIClient
    63  	sync.RWMutex
    64  
    65  	storage          metadata.Storage
    66  	indexer          indexer.Indexer
    67  	passwordHashCost int
    68  
    69  	initialized bool
    70  }
    71  
    72  type config struct {
    73  	GatewayAddr       string `mapstructure:"gateway_addr"`
    74  	ProviderAddr      string `mapstructure:"provider_addr"`
    75  	ServiceUserID     string `mapstructure:"service_user_id"`
    76  	ServiceUserIdp    string `mapstructure:"service_user_idp"`
    77  	MachineAuthAPIKey string `mapstructure:"machine_auth_apikey"`
    78  }
    79  
    80  // NewDefault returns a new manager instance with default dependencies
    81  func NewDefault(m map[string]interface{}) (publicshare.Manager, error) {
    82  	c := &config{}
    83  	if err := mapstructure.Decode(m, c); err != nil {
    84  		err = errors.Wrap(err, "error creating a new manager")
    85  		return nil, err
    86  	}
    87  
    88  	s, err := metadata.NewCS3Storage(c.GatewayAddr, c.ProviderAddr, c.ServiceUserID, c.ServiceUserIdp, c.MachineAuthAPIKey)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	indexer := indexer.CreateIndexer(s)
    93  
    94  	client, err := pool.GetGatewayServiceClient(c.GatewayAddr)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	return New(client, s, indexer, bcrypt.DefaultCost)
   100  }
   101  
   102  // New returns a new manager instance
   103  func New(gatewayClient gateway.GatewayAPIClient, storage metadata.Storage, indexer indexer.Indexer, passwordHashCost int) (*Manager, error) {
   104  	return &Manager{
   105  		gatewayClient:    gatewayClient,
   106  		storage:          storage,
   107  		indexer:          indexer,
   108  		passwordHashCost: passwordHashCost,
   109  		initialized:      false,
   110  	}, nil
   111  }
   112  
   113  func (m *Manager) initialize() error {
   114  	if m.initialized {
   115  		return nil
   116  	}
   117  
   118  	m.Lock()
   119  	defer m.Unlock()
   120  
   121  	if m.initialized { // check if initialization happened while grabbing the lock
   122  		return nil
   123  	}
   124  
   125  	err := m.storage.Init(context.Background(), "public-share-manager-metadata")
   126  	if err != nil {
   127  		return err
   128  	}
   129  	if err := m.storage.MakeDirIfNotExist(context.Background(), "publicshares"); err != nil {
   130  		return err
   131  	}
   132  	err = m.indexer.AddIndex(&link.PublicShare{}, option.IndexByField("Id.OpaqueId"), "Token", "publicshares", "unique", nil, true)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	err = m.indexer.AddIndex(&link.PublicShare{}, option.IndexByFunc{
   137  		Name: "Owner",
   138  		Func: indexOwnerFunc,
   139  	}, "Token", "publicshares", "non_unique", nil, true)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	err = m.indexer.AddIndex(&link.PublicShare{}, option.IndexByFunc{
   144  		Name: "Creator",
   145  		Func: indexCreatorFunc,
   146  	}, "Token", "publicshares", "non_unique", nil, true)
   147  	if err != nil {
   148  		return err
   149  	}
   150  	err = m.indexer.AddIndex(&link.PublicShare{}, option.IndexByFunc{
   151  		Name: "ResourceId",
   152  		Func: indexResourceIDFunc,
   153  	}, "Token", "publicshares", "non_unique", nil, true)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	m.initialized = true
   158  	return nil
   159  }
   160  
   161  // Dump exports public shares to channels (e.g. during migration)
   162  func (m *Manager) Dump(ctx context.Context, shareChan chan<- *publicshare.WithPassword) error {
   163  	if err := m.initialize(); err != nil {
   164  		return err
   165  	}
   166  
   167  	pshares, err := m.storage.ListDir(ctx, "publicshares")
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	for _, v := range pshares {
   173  		var local publicshare.WithPassword
   174  		ps, err := m.getByToken(ctx, v.Name)
   175  		if err != nil {
   176  			return err
   177  		}
   178  		local.Password = ps.Password
   179  		proto.Merge(&local.PublicShare, &ps.PublicShare)
   180  
   181  		shareChan <- &local
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  // Load imports public shares and received shares from channels (e.g. during migration)
   188  func (m *Manager) Load(ctx context.Context, shareChan <-chan *publicshare.WithPassword) error {
   189  	log := appctx.GetLogger(ctx)
   190  	if err := m.initialize(); err != nil {
   191  		return err
   192  	}
   193  	for ps := range shareChan {
   194  		if err := m.persist(context.Background(), ps); err != nil {
   195  			log.Error().Err(err).Interface("publicshare", ps).Msg("error loading public share")
   196  		}
   197  	}
   198  	return nil
   199  }
   200  
   201  // CreatePublicShare creates a new public share
   202  func (m *Manager) CreatePublicShare(ctx context.Context, u *user.User, ri *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error) {
   203  	if err := m.initialize(); err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	id := &link.PublicShareId{
   208  		OpaqueId: utils.RandString(15),
   209  	}
   210  
   211  	tkn := utils.RandString(15)
   212  	now := time.Now().UnixNano()
   213  
   214  	displayName, quicklink := tkn, false
   215  	if ri.ArbitraryMetadata != nil {
   216  		metadataName, ok := ri.ArbitraryMetadata.Metadata["name"]
   217  		if ok {
   218  			displayName = metadataName
   219  		}
   220  
   221  		quicklink, _ = strconv.ParseBool(ri.ArbitraryMetadata.Metadata["quicklink"])
   222  	}
   223  
   224  	var passwordProtected bool
   225  	password := g.Password
   226  	if password != "" {
   227  		h, err := bcrypt.GenerateFromPassword([]byte(password), m.passwordHashCost)
   228  		if err != nil {
   229  			return nil, errors.Wrap(err, "could not hash share password")
   230  		}
   231  		password = string(h)
   232  		passwordProtected = true
   233  	}
   234  
   235  	createdAt := &typespb.Timestamp{
   236  		Seconds: uint64(now / int64(time.Second)),
   237  		Nanos:   uint32(now % int64(time.Second)),
   238  	}
   239  
   240  	s := &publicshare.WithPassword{
   241  		PublicShare: link.PublicShare{
   242  			Id:                id,
   243  			Owner:             ri.GetOwner(),
   244  			Creator:           u.Id,
   245  			ResourceId:        ri.Id,
   246  			Token:             tkn,
   247  			Permissions:       g.Permissions,
   248  			Ctime:             createdAt,
   249  			Mtime:             createdAt,
   250  			PasswordProtected: passwordProtected,
   251  			Expiration:        g.Expiration,
   252  			DisplayName:       displayName,
   253  			Quicklink:         quicklink,
   254  		},
   255  		Password: password,
   256  	}
   257  
   258  	err := m.persist(ctx, s)
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  
   263  	return &s.PublicShare, nil
   264  }
   265  
   266  // UpdatePublicShare updates an existing public share
   267  func (m *Manager) UpdatePublicShare(ctx context.Context, u *user.User, req *link.UpdatePublicShareRequest) (*link.PublicShare, error) {
   268  	if err := m.initialize(); err != nil {
   269  		return nil, err
   270  	}
   271  
   272  	ps, err := m.getWithPassword(ctx, req.Ref)
   273  	if err != nil {
   274  		return nil, err
   275  	}
   276  
   277  	switch req.Update.Type {
   278  	case link.UpdatePublicShareRequest_Update_TYPE_DISPLAYNAME:
   279  		ps.PublicShare.DisplayName = req.Update.DisplayName
   280  	case link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS:
   281  		ps.PublicShare.Permissions = req.Update.Grant.Permissions
   282  	case link.UpdatePublicShareRequest_Update_TYPE_EXPIRATION:
   283  		ps.PublicShare.Expiration = req.Update.Grant.Expiration
   284  	case link.UpdatePublicShareRequest_Update_TYPE_PASSWORD:
   285  		if req.Update.Grant.Password == "" {
   286  			ps.Password = ""
   287  			ps.PublicShare.PasswordProtected = false
   288  		} else {
   289  			h, err := bcrypt.GenerateFromPassword([]byte(req.Update.Grant.Password), m.passwordHashCost)
   290  			if err != nil {
   291  				return nil, errors.Wrap(err, "could not hash share password")
   292  			}
   293  			ps.Password = string(h)
   294  			ps.PublicShare.PasswordProtected = true
   295  		}
   296  	default:
   297  		return nil, errtypes.BadRequest("no valid update type given")
   298  	}
   299  
   300  	err = m.persist(ctx, ps)
   301  	if err != nil {
   302  		return nil, err
   303  	}
   304  
   305  	return &ps.PublicShare, nil
   306  }
   307  
   308  // GetPublicShare returns an existing public share
   309  func (m *Manager) GetPublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference, sign bool) (*link.PublicShare, error) {
   310  	if err := m.initialize(); err != nil {
   311  		return nil, err
   312  	}
   313  
   314  	ps, err := m.getWithPassword(ctx, ref)
   315  	if err != nil {
   316  		return nil, err
   317  	}
   318  
   319  	if ps.PublicShare.PasswordProtected && sign {
   320  		err = publicshare.AddSignature(&ps.PublicShare, ps.Password)
   321  		if err != nil {
   322  			return nil, err
   323  		}
   324  	}
   325  
   326  	return &ps.PublicShare, nil
   327  }
   328  
   329  func (m *Manager) getWithPassword(ctx context.Context, ref *link.PublicShareReference) (*publicshare.WithPassword, error) {
   330  	switch {
   331  	case ref.GetToken() != "":
   332  		return m.getByToken(ctx, ref.GetToken())
   333  	case ref.GetId().GetOpaqueId() != "":
   334  		return m.getByID(ctx, ref.GetId().GetOpaqueId())
   335  	default:
   336  		return nil, errtypes.BadRequest("neither id nor token given")
   337  	}
   338  }
   339  
   340  func (m *Manager) getByID(ctx context.Context, id string) (*publicshare.WithPassword, error) {
   341  	tokens, err := m.indexer.FindBy(&link.PublicShare{},
   342  		indexer.NewField("Id.OpaqueId", id),
   343  	)
   344  	if err != nil {
   345  		return nil, err
   346  	}
   347  	if len(tokens) == 0 {
   348  		return nil, errtypes.NotFound("publicshare with the given id not found")
   349  	}
   350  	return m.getByToken(ctx, tokens[0])
   351  }
   352  
   353  func (m *Manager) getByToken(ctx context.Context, token string) (*publicshare.WithPassword, error) {
   354  	fn := path.Join("publicshares", token)
   355  	data, err := m.storage.SimpleDownload(ctx, fn)
   356  	if err != nil {
   357  		return nil, err
   358  	}
   359  
   360  	ps := &publicshare.WithPassword{}
   361  	err = json.Unmarshal(data, ps)
   362  	if err != nil {
   363  		return nil, err
   364  	}
   365  	id := storagespace.UpdateLegacyResourceID(ps.PublicShare.ResourceId)
   366  	ps.PublicShare.ResourceId = id
   367  	return ps, nil
   368  }
   369  
   370  // ListPublicShares lists existing public shares matching the given filters
   371  func (m *Manager) ListPublicShares(ctx context.Context, u *user.User, filters []*link.ListPublicSharesRequest_Filter, sign bool) ([]*link.PublicShare, error) {
   372  	if err := m.initialize(); err != nil {
   373  		return nil, err
   374  	}
   375  
   376  	log := appctx.GetLogger(ctx)
   377  	var rIDs []*provider.ResourceId
   378  	if len(filters) != 0 {
   379  		grouped := publicshare.GroupFiltersByType(filters)
   380  		for _, g := range grouped {
   381  			for _, f := range g {
   382  				if f.GetResourceId() != nil {
   383  					rIDs = append(rIDs, f.GetResourceId())
   384  				}
   385  			}
   386  		}
   387  	}
   388  	var (
   389  		createdShareTokens []string
   390  		err                error
   391  	)
   392  
   393  	// in spaces, always use the resourceId
   394  	if len(rIDs) != 0 {
   395  		for _, rID := range rIDs {
   396  			shareTokens, err := m.indexer.FindBy(&link.PublicShare{},
   397  				indexer.NewField("ResourceId", resourceIDToIndex(rID)),
   398  			)
   399  			if err != nil {
   400  				return nil, err
   401  			}
   402  			createdShareTokens = append(createdShareTokens, shareTokens...)
   403  		}
   404  	} else {
   405  		// fallback for legacy use
   406  		createdShareTokens, err = m.indexer.FindBy(&link.PublicShare{},
   407  			indexer.NewField("Owner", userIDToIndex(u.Id)),
   408  			indexer.NewField("Creator", userIDToIndex(u.Id)),
   409  		)
   410  		if err != nil {
   411  			return nil, err
   412  		}
   413  	}
   414  
   415  	// We use shareMem as a temporary lookup store to check which shares were
   416  	// already added. This is to prevent duplicates.
   417  	shareMem := make(map[string]struct{})
   418  	result := []*link.PublicShare{}
   419  	for _, token := range createdShareTokens {
   420  		ps, err := m.getByToken(ctx, token)
   421  		if err != nil {
   422  			return nil, err
   423  		}
   424  
   425  		if !publicshare.MatchesFilters(&ps.PublicShare, filters) {
   426  			continue
   427  		}
   428  
   429  		if publicshare.IsExpired(&ps.PublicShare) {
   430  			ref := &link.PublicShareReference{
   431  				Spec: &link.PublicShareReference_Id{
   432  					Id: ps.PublicShare.Id,
   433  				},
   434  			}
   435  			if err := m.RevokePublicShare(ctx, u, ref); err != nil {
   436  				log.Error().Err(err).
   437  					Str("public_share_token", ps.PublicShare.Token).
   438  					Str("public_share_id", ps.PublicShare.Id.OpaqueId).
   439  					Msg("failed to revoke expired public share")
   440  			}
   441  			continue
   442  		}
   443  
   444  		if ps.PublicShare.PasswordProtected && sign {
   445  			err = publicshare.AddSignature(&ps.PublicShare, ps.Password)
   446  			if err != nil {
   447  				return nil, err
   448  			}
   449  		}
   450  		result = append(result, &ps.PublicShare)
   451  		shareMem[ps.PublicShare.Token] = struct{}{}
   452  	}
   453  
   454  	// If a user requests to list shares which have not been created by them
   455  	// we have to explicitly fetch these shares and check if the user is
   456  	// allowed to list the shares.
   457  	// Only then can we add these shares to the result.
   458  	grouped := publicshare.GroupFiltersByType(filters)
   459  	idFilter, ok := grouped[link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID]
   460  	if !ok {
   461  		return result, nil
   462  	}
   463  
   464  	var tokens []string
   465  	if len(idFilter) > 0 {
   466  		idFilters := make([]indexer.Field, 0, len(idFilter))
   467  		for _, filter := range idFilter {
   468  			resourceID := filter.GetResourceId()
   469  			idFilters = append(idFilters, indexer.NewField("ResourceId", resourceIDToIndex(resourceID)))
   470  		}
   471  		tokens, err = m.indexer.FindBy(&link.PublicShare{}, idFilters...)
   472  		if err != nil {
   473  			return nil, err
   474  		}
   475  	}
   476  
   477  	// statMem is used as a local cache to prevent statting resources which
   478  	// already have been checked.
   479  	statMem := make(map[string]struct{})
   480  	for _, token := range tokens {
   481  		if _, handled := shareMem[token]; handled {
   482  			// We don't want to add a share multiple times when we added it
   483  			// already.
   484  			continue
   485  		}
   486  
   487  		s, err := m.getByToken(ctx, token)
   488  		if err != nil {
   489  			return nil, err
   490  		}
   491  
   492  		if _, checked := statMem[resourceIDToIndex(s.PublicShare.GetResourceId())]; !checked {
   493  			sReq := &provider.StatRequest{
   494  				Ref: &provider.Reference{ResourceId: s.PublicShare.GetResourceId()},
   495  			}
   496  			sRes, err := m.gatewayClient.Stat(ctx, sReq)
   497  			if err != nil {
   498  				continue
   499  			}
   500  			if sRes.Status.Code != rpc.Code_CODE_OK {
   501  				continue
   502  			}
   503  			if !sRes.Info.PermissionSet.ListGrants {
   504  				continue
   505  			}
   506  			statMem[resourceIDToIndex(s.PublicShare.GetResourceId())] = struct{}{}
   507  		}
   508  
   509  		if publicshare.MatchesFilters(&s.PublicShare, filters) {
   510  			result = append(result, &s.PublicShare)
   511  			shareMem[s.PublicShare.Token] = struct{}{}
   512  		}
   513  	}
   514  	return result, nil
   515  }
   516  
   517  // RevokePublicShare revokes an existing public share
   518  func (m *Manager) RevokePublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference) error {
   519  	if err := m.initialize(); err != nil {
   520  		return err
   521  	}
   522  
   523  	ps, err := m.GetPublicShare(ctx, u, ref, false)
   524  	if err != nil {
   525  		return err
   526  	}
   527  
   528  	err = m.storage.Delete(ctx, path.Join("publicshares", ps.Token))
   529  	if err != nil {
   530  		if _, ok := err.(errtypes.NotFound); !ok {
   531  			return err
   532  		}
   533  	}
   534  
   535  	return m.indexer.Delete(ps)
   536  }
   537  
   538  // GetPublicShareByToken gets an existing public share in an unauthenticated context using either a password or a signature
   539  func (m *Manager) GetPublicShareByToken(ctx context.Context, token string, auth *link.PublicShareAuthentication, sign bool) (*link.PublicShare, error) {
   540  	if err := m.initialize(); err != nil {
   541  		return nil, err
   542  	}
   543  
   544  	ps, err := m.getByToken(ctx, token)
   545  	if err != nil {
   546  		return nil, err
   547  	}
   548  
   549  	if publicshare.IsExpired(&ps.PublicShare) {
   550  		return nil, errtypes.NotFound("public share has expired")
   551  	}
   552  
   553  	if ps.PublicShare.PasswordProtected {
   554  		if !publicshare.Authenticate(&ps.PublicShare, ps.Password, auth) {
   555  			return nil, errtypes.InvalidCredentials("access denied")
   556  		}
   557  	}
   558  
   559  	return &ps.PublicShare, nil
   560  }
   561  
   562  func indexOwnerFunc(v interface{}) (string, error) {
   563  	ps, ok := v.(*link.PublicShare)
   564  	if !ok {
   565  		return "", fmt.Errorf("given entity is not a public share")
   566  	}
   567  	return userIDToIndex(ps.Owner), nil
   568  }
   569  
   570  func indexCreatorFunc(v interface{}) (string, error) {
   571  	ps, ok := v.(*link.PublicShare)
   572  	if !ok {
   573  		return "", fmt.Errorf("given entity is not a public share")
   574  	}
   575  	return userIDToIndex(ps.Creator), nil
   576  }
   577  
   578  func indexResourceIDFunc(v interface{}) (string, error) {
   579  	ps, ok := v.(*link.PublicShare)
   580  	if !ok {
   581  		return "", fmt.Errorf("given entity is not a public share")
   582  	}
   583  	return resourceIDToIndex(ps.ResourceId), nil
   584  }
   585  
   586  func userIDToIndex(id *user.UserId) string {
   587  	return url.QueryEscape(id.Idp + ":" + id.OpaqueId)
   588  }
   589  
   590  func resourceIDToIndex(id *provider.ResourceId) string {
   591  	return strings.Join([]string{id.StorageId, id.OpaqueId}, "!")
   592  }
   593  
   594  func (m *Manager) persist(ctx context.Context, ps *publicshare.WithPassword) error {
   595  	data, err := json.Marshal(ps)
   596  	if err != nil {
   597  		return err
   598  	}
   599  
   600  	fn := path.Join("publicshares", ps.PublicShare.Token)
   601  	err = m.storage.SimpleUpload(ctx, fn, data)
   602  	if err != nil {
   603  		return err
   604  	}
   605  
   606  	_, err = m.indexer.Add(&ps.PublicShare)
   607  	if err != nil {
   608  		if _, ok := err.(*indexerErrors.AlreadyExistsErr); ok {
   609  			return nil
   610  		}
   611  		err = m.indexer.Delete(&ps.PublicShare)
   612  		if err != nil {
   613  			return err
   614  		}
   615  		_, err = m.indexer.Add(&ps.PublicShare)
   616  		return err
   617  	}
   618  
   619  	return nil
   620  }