Use the opencloud reva from now on
This commit is contained in:
+620
@@ -0,0 +1,620 @@
|
||||
// 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
|
||||
}
|
||||
+727
@@ -0,0 +1,727 @@
|
||||
// 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 json
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
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/mitchellh/mapstructure"
|
||||
"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/json/persistence"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/json/persistence/cs3"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/json/persistence/file"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/json/persistence/memory"
|
||||
"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/metadata"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register("json", NewFile)
|
||||
registry.Register("jsoncs3", NewCS3)
|
||||
registry.Register("jsonmemory", NewMemory)
|
||||
}
|
||||
|
||||
// NewFile returns a new filesystem public shares manager.
|
||||
func NewFile(c map[string]interface{}) (publicshare.Manager, error) {
|
||||
conf := &fileConfig{}
|
||||
if err := mapstructure.Decode(c, conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf.init()
|
||||
if conf.File == "" {
|
||||
conf.File = "/var/tmp/reva/publicshares"
|
||||
}
|
||||
|
||||
p := file.New(conf.File)
|
||||
return New(conf.GatewayAddr, conf.SharePasswordHashCost, conf.JanitorRunInterval, conf.EnableExpiredSharesCleanup, p)
|
||||
}
|
||||
|
||||
// NewMemory returns a new in-memory public shares manager.
|
||||
func NewMemory(c map[string]interface{}) (publicshare.Manager, error) {
|
||||
conf := &commonConfig{}
|
||||
if err := mapstructure.Decode(c, conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf.init()
|
||||
p := memory.New()
|
||||
|
||||
return New(conf.GatewayAddr, conf.SharePasswordHashCost, conf.JanitorRunInterval, conf.EnableExpiredSharesCleanup, p)
|
||||
}
|
||||
|
||||
// NewCS3 returns a new cs3 public shares manager.
|
||||
func NewCS3(c map[string]interface{}) (publicshare.Manager, error) {
|
||||
conf := &cs3Config{}
|
||||
if err := mapstructure.Decode(c, conf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf.init()
|
||||
|
||||
s, err := metadata.NewCS3Storage(conf.ProviderAddr, conf.ProviderAddr, conf.ServiceUserID, conf.ServiceUserIdp, conf.MachineAuthAPIKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := cs3.New(s)
|
||||
|
||||
return New(conf.GatewayAddr, conf.SharePasswordHashCost, conf.JanitorRunInterval, conf.EnableExpiredSharesCleanup, p)
|
||||
}
|
||||
|
||||
// New returns a new public share manager instance
|
||||
func New(gwAddr string, pwHashCost, janitorRunInterval int, enableCleanup bool, p persistence.Persistence) (publicshare.Manager, error) {
|
||||
m := &manager{
|
||||
gatewayAddr: gwAddr,
|
||||
mutex: &sync.Mutex{},
|
||||
passwordHashCost: pwHashCost,
|
||||
janitorRunInterval: janitorRunInterval,
|
||||
enableExpiredSharesCleanup: enableCleanup,
|
||||
persistence: p,
|
||||
}
|
||||
|
||||
go m.startJanitorRun()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
type commonConfig struct {
|
||||
GatewayAddr string `mapstructure:"gateway_addr"`
|
||||
SharePasswordHashCost int `mapstructure:"password_hash_cost"`
|
||||
JanitorRunInterval int `mapstructure:"janitor_run_interval"`
|
||||
EnableExpiredSharesCleanup bool `mapstructure:"enable_expired_shares_cleanup"`
|
||||
}
|
||||
|
||||
type fileConfig struct {
|
||||
commonConfig `mapstructure:",squash"`
|
||||
|
||||
File string `mapstructure:"file"`
|
||||
}
|
||||
|
||||
type cs3Config struct {
|
||||
commonConfig `mapstructure:",squash"`
|
||||
|
||||
ProviderAddr string `mapstructure:"provider_addr"`
|
||||
ServiceUserID string `mapstructure:"service_user_id"`
|
||||
ServiceUserIdp string `mapstructure:"service_user_idp"`
|
||||
MachineAuthAPIKey string `mapstructure:"machine_auth_apikey"`
|
||||
}
|
||||
|
||||
func (c *commonConfig) init() {
|
||||
if c.SharePasswordHashCost == 0 {
|
||||
c.SharePasswordHashCost = 11
|
||||
}
|
||||
if c.JanitorRunInterval == 0 {
|
||||
c.JanitorRunInterval = 60
|
||||
}
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
gatewayAddr string
|
||||
mutex *sync.Mutex
|
||||
persistence persistence.Persistence
|
||||
|
||||
passwordHashCost int
|
||||
janitorRunInterval int
|
||||
enableExpiredSharesCleanup bool
|
||||
}
|
||||
|
||||
func (m *manager) init() error {
|
||||
return m.persistence.Init(context.Background())
|
||||
}
|
||||
|
||||
func (m *manager) startJanitorRun() {
|
||||
if !m.enableExpiredSharesCleanup {
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(time.Duration(m.janitorRunInterval) * time.Second)
|
||||
work := make(chan os.Signal, 1)
|
||||
signal.Notify(work, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-work:
|
||||
return
|
||||
case <-ticker.C:
|
||||
m.cleanupExpiredShares()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dump exports public shares to channels (e.g. during migration)
|
||||
func (m *manager) Dump(ctx context.Context, shareChan chan<- *publicshare.WithPassword) error {
|
||||
log := appctx.GetLogger(ctx)
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if err := m.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db, err := m.persistence.Read(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range db {
|
||||
var local publicshare.WithPassword
|
||||
if err := utils.UnmarshalJSONToProtoV1([]byte(v.(map[string]interface{})["share"].(string)), &local.PublicShare); err != nil {
|
||||
log.Error().Err(err).Msg("error unmarshalling share")
|
||||
}
|
||||
local.Password = v.(map[string]interface{})["password"].(string)
|
||||
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 {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if err := m.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db, err := m.persistence.Read(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for ps := range shareChan {
|
||||
encShare, err := utils.MarshalProtoV1ToJSON(&ps.PublicShare)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db[ps.PublicShare.Id.GetOpaqueId()] = map[string]interface{}{
|
||||
"share": string(encShare),
|
||||
"password": ps.Password,
|
||||
}
|
||||
}
|
||||
return m.persistence.Write(ctx, db)
|
||||
}
|
||||
|
||||
// CreatePublicShare adds a new entry to manager.shares
|
||||
func (m *manager) CreatePublicShare(ctx context.Context, u *user.User, rInfo *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error) {
|
||||
id := &link.PublicShareId{
|
||||
OpaqueId: utils.RandString(15),
|
||||
}
|
||||
|
||||
tkn := utils.RandString(15)
|
||||
now := time.Now().UnixNano()
|
||||
|
||||
displayName, ok := rInfo.ArbitraryMetadata.Metadata["name"]
|
||||
if !ok {
|
||||
displayName = tkn
|
||||
}
|
||||
|
||||
quicklink, _ := strconv.ParseBool(rInfo.ArbitraryMetadata.Metadata["quicklink"])
|
||||
|
||||
var passwordProtected bool
|
||||
password := g.Password
|
||||
if len(password) > 0 {
|
||||
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 := &link.PublicShare{
|
||||
Id: id,
|
||||
Owner: rInfo.GetOwner(),
|
||||
Creator: u.Id,
|
||||
ResourceId: rInfo.Id,
|
||||
Token: tkn,
|
||||
Permissions: g.Permissions,
|
||||
Ctime: createdAt,
|
||||
Mtime: createdAt,
|
||||
PasswordProtected: passwordProtected,
|
||||
Expiration: g.Expiration,
|
||||
DisplayName: displayName,
|
||||
Quicklink: quicklink,
|
||||
}
|
||||
|
||||
ps := &publicShare{
|
||||
Password: password,
|
||||
}
|
||||
proto.Merge(&ps.PublicShare, s)
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if err := m.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encShare, err := utils.MarshalProtoV1ToJSON(&ps.PublicShare)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := m.persistence.Read(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := db[s.Id.GetOpaqueId()]; !ok {
|
||||
db[s.Id.GetOpaqueId()] = map[string]interface{}{
|
||||
"share": string(encShare),
|
||||
"password": ps.Password,
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("key already exists")
|
||||
}
|
||||
|
||||
err = m.persistence.Write(ctx, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// UpdatePublicShare updates the public share
|
||||
func (m *manager) UpdatePublicShare(ctx context.Context, u *user.User, req *link.UpdatePublicShareRequest) (*link.PublicShare, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
share, err := m.GetPublicShare(ctx, u, req.Ref, false)
|
||||
if err != nil {
|
||||
return nil, errors.New("ref does not exist")
|
||||
}
|
||||
|
||||
now := time.Now().UnixNano()
|
||||
var newPasswordEncoded string
|
||||
passwordChanged := false
|
||||
|
||||
switch req.GetUpdate().GetType() {
|
||||
case link.UpdatePublicShareRequest_Update_TYPE_DISPLAYNAME:
|
||||
log.Debug().Str("json", "update display name").Msgf("from: `%v` to `%v`", share.DisplayName, req.Update.GetDisplayName())
|
||||
share.DisplayName = req.Update.GetDisplayName()
|
||||
case link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS:
|
||||
old, _ := json.Marshal(share.Permissions)
|
||||
new, _ := json.Marshal(req.Update.GetGrant().Permissions)
|
||||
|
||||
if req.GetUpdate().GetGrant().GetPassword() != "" {
|
||||
passwordChanged = true
|
||||
h, err := bcrypt.GenerateFromPassword([]byte(req.Update.GetGrant().Password), m.passwordHashCost)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not hash share password")
|
||||
}
|
||||
newPasswordEncoded = string(h)
|
||||
share.PasswordProtected = true
|
||||
}
|
||||
|
||||
log.Debug().Str("json", "update grants").Msgf("from: `%v`\nto\n`%v`", old, new)
|
||||
share.Permissions = req.Update.GetGrant().GetPermissions()
|
||||
case link.UpdatePublicShareRequest_Update_TYPE_EXPIRATION:
|
||||
old, _ := json.Marshal(share.Expiration)
|
||||
new, _ := json.Marshal(req.Update.GetGrant().Expiration)
|
||||
log.Debug().Str("json", "update expiration").Msgf("from: `%v`\nto\n`%v`", old, new)
|
||||
share.Expiration = req.Update.GetGrant().Expiration
|
||||
case link.UpdatePublicShareRequest_Update_TYPE_PASSWORD:
|
||||
passwordChanged = true
|
||||
if req.Update.GetGrant().Password == "" {
|
||||
share.PasswordProtected = false
|
||||
newPasswordEncoded = ""
|
||||
} else {
|
||||
h, err := bcrypt.GenerateFromPassword([]byte(req.Update.GetGrant().Password), m.passwordHashCost)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not hash share password")
|
||||
}
|
||||
newPasswordEncoded = string(h)
|
||||
share.PasswordProtected = true
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid update type: %v", req.GetUpdate().GetType())
|
||||
}
|
||||
|
||||
share.Mtime = &typespb.Timestamp{
|
||||
Seconds: uint64(now / int64(time.Second)),
|
||||
Nanos: uint32(now % int64(time.Second)),
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if err := m.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := m.persistence.Read(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encShare, err := utils.MarshalProtoV1ToJSON(share)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, ok := db[share.Id.OpaqueId].(map[string]interface{})
|
||||
if !ok {
|
||||
data = map[string]interface{}{}
|
||||
}
|
||||
|
||||
if ok && passwordChanged {
|
||||
data["password"] = newPasswordEncoded
|
||||
}
|
||||
data["share"] = string(encShare)
|
||||
|
||||
db[share.Id.OpaqueId] = data
|
||||
|
||||
err = m.persistence.Write(ctx, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return share, nil
|
||||
}
|
||||
|
||||
// GetPublicShare gets a public share either by ID or Token.
|
||||
func (m *manager) GetPublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference, sign bool) (*link.PublicShare, error) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if err := m.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ref.GetToken() != "" {
|
||||
ps, pw, err := m.getByToken(ctx, ref.GetToken())
|
||||
if err != nil {
|
||||
return nil, errtypes.NotFound("no shares found by token")
|
||||
}
|
||||
if ps.PasswordProtected && sign {
|
||||
err := publicshare.AddSignature(ps, pw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ps, nil
|
||||
}
|
||||
|
||||
db, err := m.persistence.Read(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, v := range db {
|
||||
d := v.(map[string]interface{})["share"]
|
||||
passDB := v.(map[string]interface{})["password"].(string)
|
||||
|
||||
var ps link.PublicShare
|
||||
if err := utils.UnmarshalJSONToProtoV1([]byte(d.(string)), &ps); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ref.GetId().GetOpaqueId() == ps.Id.OpaqueId {
|
||||
if publicshare.IsExpired(&ps) {
|
||||
if err := m.revokeExpiredPublicShare(ctx, &ps); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errtypes.NotFound("no shares found by id:" + ref.GetId().String())
|
||||
}
|
||||
if ps.PasswordProtected && sign {
|
||||
err := publicshare.AddSignature(&ps, passDB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &ps, nil
|
||||
}
|
||||
|
||||
}
|
||||
return nil, errtypes.NotFound("no shares found by id:" + ref.GetId().String())
|
||||
}
|
||||
|
||||
// ListPublicShares retrieves all the shares on the manager that are valid.
|
||||
func (m *manager) ListPublicShares(ctx context.Context, u *user.User, filters []*link.ListPublicSharesRequest_Filter, sign bool) ([]*link.PublicShare, error) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if err := m.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log := appctx.GetLogger(ctx)
|
||||
|
||||
db, err := m.persistence.Read(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := pool.GetGatewayServiceClient(m.gatewayAddr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to list shares")
|
||||
}
|
||||
cache := make(map[string]struct{})
|
||||
|
||||
shares := []*link.PublicShare{}
|
||||
for _, v := range db {
|
||||
var local publicShare
|
||||
if err := utils.UnmarshalJSONToProtoV1([]byte(v.(map[string]interface{})["share"].(string)), &local.PublicShare); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if publicshare.IsExpired(&local.PublicShare) {
|
||||
if err := m.revokeExpiredPublicShare(ctx, &local.PublicShare); err != nil {
|
||||
log.Error().Err(err).
|
||||
Str("share_token", local.Token).
|
||||
Msg("failed to revoke expired public share")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !publicshare.MatchesFilters(&local.PublicShare, filters) {
|
||||
continue
|
||||
}
|
||||
|
||||
key := strings.Join([]string{local.ResourceId.StorageId, local.ResourceId.OpaqueId}, "!")
|
||||
if _, hit := cache[key]; !hit && !publicshare.IsCreatedByUser(&local.PublicShare, u) {
|
||||
sRes, err := client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ResourceId: local.ResourceId}})
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Err(err).
|
||||
Interface("resource_id", local.ResourceId).
|
||||
Msg("ListShares: an error occurred during stat on the resource")
|
||||
continue
|
||||
}
|
||||
if sRes.Status.Code != rpc.Code_CODE_OK {
|
||||
if sRes.Status.Code == rpc.Code_CODE_NOT_FOUND {
|
||||
log.Debug().
|
||||
Str("message", sRes.Status.Message).
|
||||
Interface("status", sRes.Status).
|
||||
Interface("resource_id", local.ResourceId).
|
||||
Msg("ListShares: Resource not found")
|
||||
continue
|
||||
}
|
||||
log.Error().
|
||||
Str("message", sRes.Status.Message).
|
||||
Interface("status", sRes.Status).
|
||||
Interface("resource_id", local.ResourceId).
|
||||
Msg("ListShares: could not stat resource")
|
||||
continue
|
||||
}
|
||||
if !sRes.Info.PermissionSet.ListGrants {
|
||||
// skip because the user doesn't have the permissions to list
|
||||
// shares of this file.
|
||||
continue
|
||||
}
|
||||
cache[key] = struct{}{}
|
||||
}
|
||||
|
||||
if local.PublicShare.PasswordProtected && sign {
|
||||
if err := publicshare.AddSignature(&local.PublicShare, local.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
shares = append(shares, &local.PublicShare)
|
||||
}
|
||||
return shares, nil
|
||||
}
|
||||
|
||||
func (m *manager) cleanupExpiredShares() {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if err := m.init(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
db, _ := m.persistence.Read(context.Background())
|
||||
|
||||
for _, v := range db {
|
||||
d := v.(map[string]interface{})["share"]
|
||||
|
||||
var ps link.PublicShare
|
||||
_ = utils.UnmarshalJSONToProtoV1([]byte(d.(string)), &ps)
|
||||
|
||||
if publicshare.IsExpired(&ps) {
|
||||
_ = m.revokeExpiredPublicShare(context.Background(), &ps)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// revokeExpiredPublicShare doesn't have a lock inside, ensure a lock before call
|
||||
func (m *manager) revokeExpiredPublicShare(ctx context.Context, s *link.PublicShare) error {
|
||||
if !m.enableExpiredSharesCleanup {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := m.revokePublicShare(ctx, &link.PublicShareReference{
|
||||
Spec: &link.PublicShareReference_Id{
|
||||
Id: &link.PublicShareId{
|
||||
OpaqueId: s.Id.OpaqueId,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
log.Err(err).Msg(fmt.Sprintf("publicShareJSONManager: error deleting public share with opaqueId: %s", s.Id.OpaqueId))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RevokePublicShare undocumented.
|
||||
func (m *manager) RevokePublicShare(ctx context.Context, _ *user.User, ref *link.PublicShareReference) error {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if err := m.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return m.revokePublicShare(ctx, ref)
|
||||
}
|
||||
|
||||
// revokePublicShare doesn't have a lock inside, ensure a lock before call
|
||||
func (m *manager) revokePublicShare(ctx context.Context, ref *link.PublicShareReference) error {
|
||||
db, err := m.persistence.Read(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case ref.GetId() != nil && ref.GetId().OpaqueId != "":
|
||||
if _, ok := db[ref.GetId().OpaqueId]; ok {
|
||||
delete(db, ref.GetId().OpaqueId)
|
||||
} else {
|
||||
return errors.New("reference does not exist")
|
||||
}
|
||||
case ref.GetToken() != "":
|
||||
share, _, err := m.getByToken(ctx, ref.GetToken())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(db, share.Id.OpaqueId)
|
||||
default:
|
||||
return errors.New("reference does not exist")
|
||||
}
|
||||
|
||||
return m.persistence.Write(ctx, db)
|
||||
}
|
||||
|
||||
// getByToken doesn't have a lock inside, ensure a lock before call
|
||||
func (m *manager) getByToken(ctx context.Context, token string) (*link.PublicShare, string, error) {
|
||||
db, err := m.persistence.Read(ctx)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
for _, v := range db {
|
||||
var local link.PublicShare
|
||||
if err := utils.UnmarshalJSONToProtoV1([]byte(v.(map[string]interface{})["share"].(string)), &local); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if local.Token == token {
|
||||
passDB := v.(map[string]interface{})["password"].(string)
|
||||
return &local, passDB, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, "", fmt.Errorf("share with token: `%v` not found", token)
|
||||
}
|
||||
|
||||
// GetPublicShareByToken gets a public share by its opaque token.
|
||||
func (m *manager) GetPublicShareByToken(ctx context.Context, token string, auth *link.PublicShareAuthentication, sign bool) (*link.PublicShare, error) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
if err := m.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := m.persistence.Read(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, v := range db {
|
||||
passDB := v.(map[string]interface{})["password"].(string)
|
||||
var local link.PublicShare
|
||||
if err := utils.UnmarshalJSONToProtoV1([]byte(v.(map[string]interface{})["share"].(string)), &local); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if local.Token == token {
|
||||
if publicshare.IsExpired(&local) {
|
||||
if err := m.revokeExpiredPublicShare(ctx, &local); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if local.PasswordProtected {
|
||||
if publicshare.Authenticate(&local, passDB, auth) {
|
||||
if sign {
|
||||
err := publicshare.AddSignature(&local, passDB)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &local, nil
|
||||
}
|
||||
|
||||
return nil, errtypes.InvalidCredentials("json: invalid password")
|
||||
}
|
||||
return &local, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errtypes.NotFound(fmt.Sprintf("share with token: `%v` not found", token))
|
||||
}
|
||||
|
||||
type publicShare struct {
|
||||
link.PublicShare
|
||||
Password string `json:"password"`
|
||||
}
|
||||
Generated
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
// 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 cs3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/json/persistence"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/metadata"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
)
|
||||
|
||||
type db struct {
|
||||
mtime time.Time
|
||||
publicShares persistence.PublicShares
|
||||
}
|
||||
|
||||
type cs3 struct {
|
||||
initialized bool
|
||||
s metadata.Storage
|
||||
|
||||
db db
|
||||
}
|
||||
|
||||
// New returns a new Cache instance
|
||||
func New(s metadata.Storage) persistence.Persistence {
|
||||
return &cs3{
|
||||
s: s,
|
||||
db: db{
|
||||
publicShares: persistence.PublicShares{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *cs3) Init(ctx context.Context) error {
|
||||
if p.initialized {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := p.s.Init(ctx, "jsoncs3-public-share-manager-metadata")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.initialized = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *cs3) Read(ctx context.Context) (persistence.PublicShares, error) {
|
||||
if !p.initialized {
|
||||
return nil, fmt.Errorf("not initialized")
|
||||
}
|
||||
|
||||
info, err := p.s.Stat(ctx, "publicshares.json")
|
||||
if err != nil {
|
||||
if _, ok := err.(errtypes.NotFound); ok {
|
||||
return p.db.publicShares, nil // Nothing to sync against
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if utils.TSToTime(info.Mtime).After(p.db.mtime) {
|
||||
readBytes, err := p.s.SimpleDownload(ctx, "publicshares.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.db.publicShares = persistence.PublicShares{}
|
||||
if err := json.Unmarshal(readBytes, &p.db.publicShares); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.db.mtime = utils.TSToTime(info.Mtime)
|
||||
}
|
||||
return p.db.publicShares, nil
|
||||
}
|
||||
|
||||
func (p *cs3) Write(ctx context.Context, db persistence.PublicShares) error {
|
||||
if !p.initialized {
|
||||
return fmt.Errorf("not initialized")
|
||||
}
|
||||
dbAsJSON, err := json.Marshal(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = p.s.Upload(ctx, metadata.UploadRequest{
|
||||
Content: dbAsJSON,
|
||||
Path: "publicshares.json",
|
||||
IfUnmodifiedSince: p.db.mtime,
|
||||
})
|
||||
return err
|
||||
}
|
||||
Generated
Vendored
+111
@@ -0,0 +1,111 @@
|
||||
// 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 file
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/json/persistence"
|
||||
)
|
||||
|
||||
type file struct {
|
||||
path string
|
||||
initialized bool
|
||||
lock *sync.RWMutex
|
||||
}
|
||||
|
||||
// New returns a new Cache instance
|
||||
func New(path string) persistence.Persistence {
|
||||
return &file{
|
||||
path: path,
|
||||
lock: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *file) Init(_ context.Context) error {
|
||||
if p.isInitialized() {
|
||||
return nil
|
||||
}
|
||||
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
if p.initialized {
|
||||
return nil
|
||||
}
|
||||
|
||||
// attempt to create the db file
|
||||
var fi os.FileInfo
|
||||
var err error
|
||||
if fi, err = os.Stat(p.path); os.IsNotExist(err) {
|
||||
folder := filepath.Dir(p.path)
|
||||
if err := os.MkdirAll(folder, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := os.Create(p.path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if fi == nil || fi.Size() == 0 {
|
||||
err := os.WriteFile(p.path, []byte("{}"), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
p.initialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *file) Read(_ context.Context) (persistence.PublicShares, error) {
|
||||
if !p.isInitialized() {
|
||||
return nil, fmt.Errorf("not initialized")
|
||||
}
|
||||
db := map[string]interface{}{}
|
||||
readBytes, err := os.ReadFile(p.path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(readBytes, &db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func (p *file) Write(_ context.Context, db persistence.PublicShares) error {
|
||||
if !p.isInitialized() {
|
||||
return fmt.Errorf("not initialized")
|
||||
}
|
||||
dbAsJSON, err := json.Marshal(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(p.path, dbAsJSON, 0644)
|
||||
}
|
||||
|
||||
func (p *file) isInitialized() bool {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
return p.initialized
|
||||
}
|
||||
Generated
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
// 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 memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/json/persistence"
|
||||
)
|
||||
|
||||
type memory struct {
|
||||
db map[string]interface{}
|
||||
}
|
||||
|
||||
// New returns a new Cache instance
|
||||
func New() persistence.Persistence {
|
||||
return &memory{
|
||||
db: map[string]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *memory) Init(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *memory) Read(_ context.Context) (persistence.PublicShares, error) {
|
||||
if p.db == nil {
|
||||
return nil, fmt.Errorf("not initialized")
|
||||
}
|
||||
return p.db, nil
|
||||
}
|
||||
func (p *memory) Write(_ context.Context, db persistence.PublicShares) error {
|
||||
if p.db == nil {
|
||||
return fmt.Errorf("not initialized")
|
||||
}
|
||||
p.db = db
|
||||
return nil
|
||||
}
|
||||
Generated
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
// 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 persistence
|
||||
|
||||
import "context"
|
||||
|
||||
// PublicShares is a map indexing publicshares by their ids
|
||||
type PublicShares map[string]interface{}
|
||||
|
||||
// Persistence defines the interface for the json publicshare manager persistence layers
|
||||
type Persistence interface {
|
||||
Init(context.Context) error
|
||||
Read(context.Context) (PublicShares, error)
|
||||
Write(context.Context, PublicShares) error
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
// 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 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"
|
||||
// Add your own here
|
||||
)
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
// 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 memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/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/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register("memory", New)
|
||||
}
|
||||
|
||||
// New returns a new memory manager.
|
||||
func New(c map[string]interface{}) (publicshare.Manager, error) {
|
||||
return &manager{
|
||||
shares: sync.Map{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
shares sync.Map
|
||||
}
|
||||
|
||||
var (
|
||||
passwordProtected bool
|
||||
)
|
||||
|
||||
// CreatePublicShare adds a new entry to manager.shares
|
||||
func (m *manager) CreatePublicShare(ctx context.Context, u *user.User, rInfo *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error) {
|
||||
id := &link.PublicShareId{
|
||||
OpaqueId: randString(15),
|
||||
}
|
||||
|
||||
tkn := randString(15)
|
||||
now := uint64(time.Now().Unix())
|
||||
|
||||
displayName, ok := rInfo.ArbitraryMetadata.Metadata["name"]
|
||||
if !ok {
|
||||
displayName = tkn
|
||||
}
|
||||
|
||||
if g.Password != "" {
|
||||
passwordProtected = true
|
||||
}
|
||||
|
||||
createdAt := &typespb.Timestamp{
|
||||
Seconds: now,
|
||||
Nanos: uint32(now % 1000000000),
|
||||
}
|
||||
|
||||
modifiedAt := &typespb.Timestamp{
|
||||
Seconds: now,
|
||||
Nanos: uint32(now % 1000000000),
|
||||
}
|
||||
|
||||
s := link.PublicShare{
|
||||
Id: id,
|
||||
Owner: rInfo.GetOwner(),
|
||||
Creator: u.Id,
|
||||
ResourceId: rInfo.Id,
|
||||
Token: tkn,
|
||||
Permissions: g.Permissions,
|
||||
Ctime: createdAt,
|
||||
Mtime: modifiedAt,
|
||||
PasswordProtected: passwordProtected,
|
||||
Expiration: g.Expiration,
|
||||
DisplayName: displayName,
|
||||
}
|
||||
|
||||
m.shares.Store(s.Token, &s)
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// UpdatePublicShare updates the expiration date, permissions and Mtime
|
||||
func (m *manager) UpdatePublicShare(ctx context.Context, u *user.User, req *link.UpdatePublicShareRequest) (*link.PublicShare, error) {
|
||||
log := appctx.GetLogger(ctx)
|
||||
share, err := m.GetPublicShare(ctx, u, req.Ref, false)
|
||||
if err != nil {
|
||||
return nil, errors.New("ref does not exist")
|
||||
}
|
||||
|
||||
token := share.GetToken()
|
||||
|
||||
switch req.GetUpdate().GetType() {
|
||||
case link.UpdatePublicShareRequest_Update_TYPE_DISPLAYNAME:
|
||||
log.Debug().Str("memory", "update display name").Msgf("from: `%v` to `%v`", share.DisplayName, req.Update.GetDisplayName())
|
||||
share.DisplayName = req.Update.GetDisplayName()
|
||||
case link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS:
|
||||
old, _ := json.Marshal(share.Permissions)
|
||||
new, _ := json.Marshal(req.Update.GetGrant().Permissions)
|
||||
log.Debug().Str("memory", "update grants").Msgf("from: `%v`\nto\n`%v`", old, new)
|
||||
share.Permissions = req.Update.GetGrant().GetPermissions()
|
||||
case link.UpdatePublicShareRequest_Update_TYPE_EXPIRATION:
|
||||
old, _ := json.Marshal(share.Expiration)
|
||||
new, _ := json.Marshal(req.Update.GetGrant().Expiration)
|
||||
log.Debug().Str("memory", "update expiration").Msgf("from: `%v`\nto\n`%v`", old, new)
|
||||
share.Expiration = req.Update.GetGrant().Expiration
|
||||
case link.UpdatePublicShareRequest_Update_TYPE_PASSWORD:
|
||||
// TODO(refs) Do public shares need Grants? Struct is defined, just not used. Fill this once it's done.
|
||||
fallthrough
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid update type: %v", req.GetUpdate().GetType())
|
||||
}
|
||||
|
||||
// share.Expiration = g.Expiration
|
||||
share.Mtime = &typespb.Timestamp{
|
||||
Seconds: uint64(time.Now().Unix()),
|
||||
Nanos: uint32(time.Now().Unix() % 1000000000),
|
||||
}
|
||||
|
||||
m.shares.Store(token, share)
|
||||
|
||||
return share, nil
|
||||
}
|
||||
|
||||
func (m *manager) GetPublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference, sign bool) (share *link.PublicShare, err error) {
|
||||
// TODO(refs) return an error if the share is expired.
|
||||
|
||||
// Attempt to fetch public share by token
|
||||
if ref.GetToken() != "" {
|
||||
share, err = m.GetPublicShareByToken(ctx, ref.GetToken(), &link.PublicShareAuthentication{}, sign)
|
||||
if err != nil {
|
||||
return nil, errors.New("no shares found by token")
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to fetch public share by Id
|
||||
if ref.GetId() != nil {
|
||||
share, err = m.getPublicShareByTokenID(ctx, ref.GetId())
|
||||
if err != nil {
|
||||
return nil, errors.New("no shares found by id")
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (m *manager) ListPublicShares(ctx context.Context, u *user.User, filters []*link.ListPublicSharesRequest_Filter, sign bool) ([]*link.PublicShare, error) {
|
||||
// TODO(refs) filter out expired shares
|
||||
shares := []*link.PublicShare{}
|
||||
m.shares.Range(func(k, v interface{}) bool {
|
||||
s := v.(*link.PublicShare)
|
||||
if len(filters) == 0 {
|
||||
shares = append(shares, s)
|
||||
} else {
|
||||
for _, f := range filters {
|
||||
if f.Type == link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID {
|
||||
if utils.ResourceIDEqual(s.ResourceId, f.GetResourceId()) {
|
||||
shares = append(shares, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return shares, nil
|
||||
}
|
||||
|
||||
func (m *manager) RevokePublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference) error {
|
||||
// check whether the reference exists
|
||||
switch {
|
||||
case ref.GetId() != nil && ref.GetId().OpaqueId != "":
|
||||
s, err := m.getPublicShareByTokenID(ctx, ref.GetId())
|
||||
if err != nil {
|
||||
return errors.New("reference does not exist")
|
||||
}
|
||||
m.shares.Delete(s.Token)
|
||||
case ref.GetToken() != "":
|
||||
if _, err := m.GetPublicShareByToken(ctx, ref.GetToken(), &link.PublicShareAuthentication{}, false); err != nil {
|
||||
return errors.New("reference does not exist")
|
||||
}
|
||||
m.shares.Delete(ref.GetToken())
|
||||
default:
|
||||
return errors.New("reference does not exist")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manager) GetPublicShareByToken(ctx context.Context, token string, auth *link.PublicShareAuthentication, sign bool) (*link.PublicShare, error) {
|
||||
if ps, ok := m.shares.Load(token); ok {
|
||||
return ps.(*link.PublicShare), nil
|
||||
}
|
||||
return nil, errtypes.NotFound("invalid token")
|
||||
}
|
||||
|
||||
func randString(n int) string {
|
||||
var l = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = l[rand.Intn(len(l))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (m *manager) getPublicShareByTokenID(ctx context.Context, targetID *link.PublicShareId) (*link.PublicShare, error) {
|
||||
var found *link.PublicShare
|
||||
m.shares.Range(func(k, v interface{}) bool {
|
||||
id := v.(*link.PublicShare).GetId()
|
||||
if targetID.String() == id.String() {
|
||||
found = v.(*link.PublicShare)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if found != nil {
|
||||
return found, nil
|
||||
}
|
||||
return nil, errors.New("resource not found")
|
||||
}
|
||||
Generated
Vendored
+226
@@ -0,0 +1,226 @@
|
||||
// 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 owncloudsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
userpb "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/jellydator/ttlcache/v2"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/conversions"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
)
|
||||
|
||||
// DBShare stores information about user and public shares.
|
||||
type DBShare struct {
|
||||
ID string
|
||||
UIDOwner string
|
||||
UIDInitiator string
|
||||
ItemStorage string
|
||||
FileSource string
|
||||
ItemType string // 'file' or 'folder'
|
||||
ShareWith string
|
||||
Token string
|
||||
Expiration string
|
||||
Permissions int
|
||||
ShareType int
|
||||
ShareName string
|
||||
STime int
|
||||
FileTarget string
|
||||
RejectedBy string
|
||||
State int
|
||||
Parent int
|
||||
}
|
||||
|
||||
// UserConverter describes an interface for converting user ids to names and back
|
||||
type UserConverter interface {
|
||||
UserNameToUserID(ctx context.Context, username string) (*userpb.UserId, error)
|
||||
UserIDToUserName(ctx context.Context, userid *userpb.UserId) (string, error)
|
||||
}
|
||||
|
||||
// GatewayUserConverter converts usernames and ids using the gateway
|
||||
type GatewayUserConverter struct {
|
||||
gwAddr string
|
||||
|
||||
IDCache *ttlcache.Cache
|
||||
NameCache *ttlcache.Cache
|
||||
}
|
||||
|
||||
// NewGatewayUserConverter returns a instance of GatewayUserConverter
|
||||
func NewGatewayUserConverter(gwAddr string) *GatewayUserConverter {
|
||||
IDCache := ttlcache.NewCache()
|
||||
_ = IDCache.SetTTL(30 * time.Second)
|
||||
IDCache.SkipTTLExtensionOnHit(true)
|
||||
NameCache := ttlcache.NewCache()
|
||||
_ = NameCache.SetTTL(30 * time.Second)
|
||||
NameCache.SkipTTLExtensionOnHit(true)
|
||||
|
||||
return &GatewayUserConverter{
|
||||
gwAddr: gwAddr,
|
||||
IDCache: IDCache,
|
||||
NameCache: NameCache,
|
||||
}
|
||||
}
|
||||
|
||||
// UserIDToUserName converts a user ID to an username
|
||||
func (c *GatewayUserConverter) UserIDToUserName(ctx context.Context, userid *userpb.UserId) (string, error) {
|
||||
username, err := c.NameCache.Get(userid.String())
|
||||
if err == nil {
|
||||
return username.(string), nil
|
||||
}
|
||||
|
||||
gwConn, err := pool.GetGatewayServiceClient(c.gwAddr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
getUserResponse, err := gwConn.GetUser(ctx, &userpb.GetUserRequest{
|
||||
UserId: userid,
|
||||
SkipFetchingUserGroups: true,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if getUserResponse.Status.Code != rpc.Code_CODE_OK {
|
||||
return "", status.NewErrorFromCode(getUserResponse.Status.Code, "gateway")
|
||||
}
|
||||
_ = c.NameCache.Set(userid.String(), getUserResponse.User.Username)
|
||||
return getUserResponse.User.Username, nil
|
||||
}
|
||||
|
||||
// UserNameToUserID converts a username to an user ID
|
||||
func (c *GatewayUserConverter) UserNameToUserID(ctx context.Context, username string) (*userpb.UserId, error) {
|
||||
id, err := c.IDCache.Get(username)
|
||||
if err == nil {
|
||||
return id.(*userpb.UserId), nil
|
||||
}
|
||||
|
||||
gwConn, err := pool.GetGatewayServiceClient(c.gwAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
getUserResponse, err := gwConn.GetUserByClaim(ctx, &userpb.GetUserByClaimRequest{
|
||||
Claim: "username",
|
||||
Value: username,
|
||||
SkipFetchingUserGroups: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if getUserResponse.Status.Code != rpc.Code_CODE_OK {
|
||||
return nil, status.NewErrorFromCode(getUserResponse.Status.Code, "gateway")
|
||||
}
|
||||
_ = c.IDCache.Set(username, getUserResponse.User.Id)
|
||||
return getUserResponse.User.Id, nil
|
||||
}
|
||||
|
||||
func resourceTypeToItem(r provider.ResourceType) string {
|
||||
switch r {
|
||||
case provider.ResourceType_RESOURCE_TYPE_FILE:
|
||||
return "file"
|
||||
case provider.ResourceType_RESOURCE_TYPE_CONTAINER:
|
||||
return "folder"
|
||||
case provider.ResourceType_RESOURCE_TYPE_REFERENCE:
|
||||
return "reference"
|
||||
case provider.ResourceType_RESOURCE_TYPE_SYMLINK:
|
||||
return "symlink"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func sharePermToInt(p *provider.ResourcePermissions) int {
|
||||
return int(conversions.RoleFromResourcePermissions(p, true).OCSPermissions())
|
||||
}
|
||||
|
||||
func intTosharePerm(p int) (*provider.ResourcePermissions, error) {
|
||||
perms, err := conversions.NewPermissions(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conversions.RoleFromOCSPermissions(perms, nil).CS3ResourcePermissions(), nil
|
||||
}
|
||||
|
||||
func formatUserID(u *userpb.UserId) string {
|
||||
return u.OpaqueId
|
||||
}
|
||||
|
||||
// ConvertToCS3PublicShare converts a DBShare to a CS3API public share
|
||||
func (m *mgr) ConvertToCS3PublicShare(ctx context.Context, s DBShare) (*link.PublicShare, error) {
|
||||
ts := &typespb.Timestamp{
|
||||
Seconds: uint64(s.STime),
|
||||
}
|
||||
permissions, err := intTosharePerm(s.Permissions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
owner, err := m.userConverter.UserNameToUserID(ctx, s.UIDOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var creator *userpb.UserId
|
||||
if s.UIDOwner == s.UIDInitiator {
|
||||
creator = owner
|
||||
} else {
|
||||
creator, err = m.userConverter.UserNameToUserID(ctx, s.UIDOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
pwd := false
|
||||
if s.ShareWith != "" {
|
||||
pwd = true
|
||||
}
|
||||
var expires *typespb.Timestamp
|
||||
if s.Expiration != "" {
|
||||
t, err := time.Parse("2006-01-02 15:04:05", s.Expiration)
|
||||
if err != nil {
|
||||
t, err = time.Parse("2006-01-02 15:04:05-07:00", s.Expiration)
|
||||
}
|
||||
if err == nil {
|
||||
expires = &typespb.Timestamp{
|
||||
Seconds: uint64(t.Unix()),
|
||||
}
|
||||
}
|
||||
}
|
||||
return &link.PublicShare{
|
||||
Id: &link.PublicShareId{
|
||||
OpaqueId: s.ID,
|
||||
},
|
||||
ResourceId: &provider.ResourceId{
|
||||
SpaceId: s.ItemStorage,
|
||||
OpaqueId: s.FileSource,
|
||||
},
|
||||
Permissions: &link.PublicSharePermissions{Permissions: permissions},
|
||||
Owner: owner,
|
||||
Creator: creator,
|
||||
Token: s.Token,
|
||||
DisplayName: s.ShareName,
|
||||
PasswordProtected: pwd,
|
||||
Expiration: expires,
|
||||
Ctime: ts,
|
||||
Mtime: ts,
|
||||
}, nil
|
||||
}
|
||||
Generated
Vendored
+533
@@ -0,0 +1,533 @@
|
||||
// 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 owncloudsql implements a publiclink share manager backed by an existing ownCloud 10 database
|
||||
//
|
||||
// The SQL queries use `coalesce({column_identifier}, ”) as {column_identifier}` to read an emptystring
|
||||
// instead of null values, which better fits the golang default values.
|
||||
package owncloudsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/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/mitchellh/mapstructure"
|
||||
"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/sharedconf"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
// Provides mysql drivers
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
const (
|
||||
publicShareType = 3
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Register("owncloudsql", NewMysql)
|
||||
}
|
||||
|
||||
// Config configures an owncloudsql publicshare manager
|
||||
type Config struct {
|
||||
GatewayAddr string `mapstructure:"gateway_addr"`
|
||||
DbUsername string `mapstructure:"db_username"`
|
||||
DbPassword string `mapstructure:"db_password"`
|
||||
DbHost string `mapstructure:"db_host"`
|
||||
DbPort int `mapstructure:"db_port"`
|
||||
DbName string `mapstructure:"db_name"`
|
||||
EnableExpiredSharesCleanup bool `mapstructure:"enable_expired_shares_cleanup"`
|
||||
SharePasswordHashCost int `mapstructure:"password_hash_cost"`
|
||||
}
|
||||
|
||||
type mgr struct {
|
||||
driver string
|
||||
db *sql.DB
|
||||
c Config
|
||||
userConverter UserConverter
|
||||
}
|
||||
|
||||
// NewMysql returns a new publicshare manager connection to a mysql database
|
||||
func NewMysql(m map[string]interface{}) (publicshare.Manager, error) {
|
||||
c, err := parseConfig(m)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "error creating a new manager")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DbUsername, c.DbPassword, c.DbHost, c.DbPort, c.DbName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userConverter := NewGatewayUserConverter(sharedconf.GetGatewaySVC(c.GatewayAddr))
|
||||
|
||||
return New("mysql", db, *c, userConverter)
|
||||
}
|
||||
|
||||
// New returns a new Cache instance connecting to the given sql.DB
|
||||
func New(driver string, db *sql.DB, c Config, userConverter UserConverter) (publicshare.Manager, error) {
|
||||
if c.SharePasswordHashCost == 0 {
|
||||
c.SharePasswordHashCost = bcrypt.DefaultCost
|
||||
}
|
||||
return &mgr{
|
||||
driver: driver,
|
||||
db: db,
|
||||
c: c,
|
||||
userConverter: userConverter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseConfig(m map[string]interface{}) (*Config, error) {
|
||||
c := &Config{}
|
||||
if err := mapstructure.Decode(m, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (m *mgr) CreatePublicShare(ctx context.Context, u *user.User, rInfo *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error) {
|
||||
|
||||
tkn := utils.RandString(15)
|
||||
now := time.Now().Unix()
|
||||
|
||||
displayName := tkn
|
||||
if rInfo.ArbitraryMetadata != nil && rInfo.ArbitraryMetadata.Metadata["name"] != "" {
|
||||
displayName = rInfo.ArbitraryMetadata.Metadata["name"]
|
||||
}
|
||||
createdAt := &typespb.Timestamp{
|
||||
Seconds: uint64(now),
|
||||
}
|
||||
|
||||
creator := u.Username
|
||||
owner, err := m.userConverter.UserIDToUserName(ctx, rInfo.Owner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
permissions := sharePermToInt(g.Permissions.Permissions)
|
||||
itemType := resourceTypeToItem(rInfo.Type)
|
||||
|
||||
itemSource := rInfo.Id.OpaqueId
|
||||
fileSource, err := strconv.ParseUint(itemSource, 10, 64)
|
||||
if err != nil {
|
||||
// it can be the case that the item source may be a character string
|
||||
// we leave fileSource blank in that case
|
||||
fileSource = 0
|
||||
}
|
||||
|
||||
columns := "share_type,uid_owner,uid_initiator,item_type,item_source,file_source,permissions,stime,token,share_name"
|
||||
placeholders := "?,?,?,?,?,?,?,?,?,?"
|
||||
params := []interface{}{publicShareType, owner, creator, itemType, itemSource, fileSource, permissions, now, tkn, displayName}
|
||||
|
||||
var passwordProtected bool
|
||||
password := g.Password
|
||||
if password != "" {
|
||||
password, err = hashPassword(password, m.c.SharePasswordHashCost)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not hash share password")
|
||||
}
|
||||
passwordProtected = true
|
||||
|
||||
columns += ",share_with"
|
||||
placeholders += ",?"
|
||||
params = append(params, password)
|
||||
}
|
||||
|
||||
if g.Expiration != nil && g.Expiration.Seconds != 0 {
|
||||
t := time.Unix(int64(g.Expiration.Seconds), 0)
|
||||
columns += ",expiration"
|
||||
placeholders += ",?"
|
||||
params = append(params, t)
|
||||
}
|
||||
|
||||
query := "INSERT INTO oc_share (" + columns + ") VALUES (" + placeholders + ")"
|
||||
stmt, err := m.db.Prepare(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err := stmt.Exec(params...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lastID, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &link.PublicShare{
|
||||
Id: &link.PublicShareId{
|
||||
OpaqueId: strconv.FormatInt(lastID, 10),
|
||||
},
|
||||
Owner: rInfo.GetOwner(),
|
||||
Creator: u.Id,
|
||||
ResourceId: rInfo.Id,
|
||||
Token: tkn,
|
||||
Permissions: g.Permissions,
|
||||
Ctime: createdAt,
|
||||
Mtime: createdAt,
|
||||
PasswordProtected: passwordProtected,
|
||||
Expiration: g.Expiration,
|
||||
DisplayName: displayName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// owncloud 10 prefixes the hash with `1|`
|
||||
func hashPassword(password string, cost int) (string, error) {
|
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), cost)
|
||||
return "1|" + string(bytes), err
|
||||
}
|
||||
|
||||
// UpdatePublicShare updates the expiration date, permissions and Mtime
|
||||
func (m *mgr) UpdatePublicShare(ctx context.Context, u *user.User, req *link.UpdatePublicShareRequest) (*link.PublicShare, error) {
|
||||
query := "update oc_share set "
|
||||
paramsMap := map[string]interface{}{}
|
||||
params := []interface{}{}
|
||||
|
||||
now := time.Now().Unix()
|
||||
uid := u.Username
|
||||
|
||||
switch req.GetUpdate().GetType() {
|
||||
case link.UpdatePublicShareRequest_Update_TYPE_DISPLAYNAME:
|
||||
paramsMap["share_name"] = req.Update.GetDisplayName()
|
||||
case link.UpdatePublicShareRequest_Update_TYPE_PERMISSIONS:
|
||||
paramsMap["permissions"] = sharePermToInt(req.Update.GetGrant().GetPermissions().Permissions)
|
||||
case link.UpdatePublicShareRequest_Update_TYPE_EXPIRATION:
|
||||
paramsMap["expiration"] = time.Unix(int64(req.Update.GetGrant().Expiration.Seconds), 0)
|
||||
case link.UpdatePublicShareRequest_Update_TYPE_PASSWORD:
|
||||
if req.Update.GetGrant().Password == "" {
|
||||
paramsMap["share_with"] = ""
|
||||
} else {
|
||||
h, err := hashPassword(req.Update.GetGrant().Password, m.c.SharePasswordHashCost)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not hash share password")
|
||||
}
|
||||
paramsMap["share_with"] = h
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid update type: %v", req.GetUpdate().GetType())
|
||||
}
|
||||
|
||||
for k, v := range paramsMap {
|
||||
query += k + "=?"
|
||||
params = append(params, v)
|
||||
}
|
||||
|
||||
switch {
|
||||
case req.Ref.GetId() != nil:
|
||||
query += ",stime=? where id=? AND (uid_owner=? or uid_initiator=?)"
|
||||
params = append(params, now, req.Ref.GetId().OpaqueId, uid, uid)
|
||||
case req.Ref.GetToken() != "":
|
||||
query += ",stime=? where token=? AND (uid_owner=? or uid_initiator=?)"
|
||||
params = append(params, now, req.Ref.GetToken(), uid, uid)
|
||||
default:
|
||||
return nil, errtypes.NotFound(req.Ref.String())
|
||||
}
|
||||
|
||||
stmt, err := m.db.Prepare(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err = stmt.Exec(params...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.GetPublicShare(ctx, u, req.Ref, false)
|
||||
}
|
||||
|
||||
func (m *mgr) GetPublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference, sign bool) (share *link.PublicShare, err error) {
|
||||
|
||||
ps, err := m.getWithPassword(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if publicshare.IsExpired(&ps.PublicShare) {
|
||||
if err := m.cleanupExpiredShares(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errtypes.NotFound("public share has expired")
|
||||
}
|
||||
|
||||
if ps.PublicShare.PasswordProtected && sign {
|
||||
err = publicshare.AddSignature(&ps.PublicShare, ps.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &ps.PublicShare, nil
|
||||
}
|
||||
|
||||
func (m *mgr) 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 *mgr) getByToken(ctx context.Context, token string) (*publicshare.WithPassword, error) {
|
||||
s, err := getByToken(m.db, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ps, err := m.ConvertToCS3PublicShare(ctx, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := &publicshare.WithPassword{
|
||||
Password: strings.TrimPrefix(s.ShareWith, "1|"),
|
||||
}
|
||||
proto.Merge(&ret.PublicShare, ps)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func getByToken(db *sql.DB, token string) (DBShare, error) {
|
||||
s := DBShare{Token: token}
|
||||
query := `SELECT
|
||||
coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator,
|
||||
coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source,
|
||||
coalesce(item_type, '') as item_type,
|
||||
coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name,
|
||||
s.id, s.stime, s.permissions, fc.storage as storage
|
||||
FROM oc_share s
|
||||
LEFT JOIN oc_filecache fc ON fc.fileid = file_source
|
||||
WHERE share_type=? AND token=?`
|
||||
if err := db.QueryRow(query, publicShareType, token).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.ItemType, &s.Expiration, &s.ShareName, &s.ID, &s.STime, &s.Permissions, &s.ItemStorage); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return s, errtypes.NotFound(token)
|
||||
}
|
||||
return s, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (m *mgr) getByID(ctx context.Context, id string) (*publicshare.WithPassword, error) {
|
||||
s := DBShare{ID: id}
|
||||
query := `SELECT
|
||||
coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator,
|
||||
coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source,
|
||||
coalesce(item_type, '') as item_type, coalesce(token,'') as token,
|
||||
coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name,
|
||||
s.stime, s.permissions, fc.storage as storage
|
||||
FROM oc_share s
|
||||
LEFT JOIN oc_filecache fc ON fc.fileid = file_source
|
||||
WHERE share_type=? AND id=?`
|
||||
if err := m.db.QueryRow(query, publicShareType, id).Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.ItemType, &s.Token, &s.Expiration, &s.ShareName, &s.STime, &s.Permissions, &s.ItemStorage); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, errtypes.NotFound(id)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
ps, err := m.ConvertToCS3PublicShare(ctx, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := &publicshare.WithPassword{
|
||||
Password: strings.TrimPrefix(s.ShareWith, "1|"),
|
||||
}
|
||||
proto.Merge(&ret.PublicShare, ps)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (m *mgr) ListPublicShares(ctx context.Context, u *user.User, filters []*link.ListPublicSharesRequest_Filter, sign bool) ([]*link.PublicShare, error) {
|
||||
uid := u.Username
|
||||
// FIXME instead of joining we may want to have to do a stat call ... if we want to store shares from other providers? or just Dump()? and be done with migration?
|
||||
query := `SELECT
|
||||
coalesce(uid_owner, '') as uid_owner, coalesce(uid_initiator, '') as uid_initiator,
|
||||
coalesce(share_with, '') as share_with, coalesce(file_source, '') as file_source,
|
||||
coalesce(item_type, '') as item_type, coalesce(token,'') as token,
|
||||
coalesce(expiration, '') as expiration, coalesce(share_name, '') as share_name,
|
||||
s.id, s.stime, s.permissions, fc.storage as storage
|
||||
FROM oc_share s
|
||||
LEFT JOIN oc_filecache fc ON fc.fileid = file_source
|
||||
WHERE (uid_owner=? or uid_initiator=?)
|
||||
AND (share_type=?)`
|
||||
var resourceFilters, ownerFilters, creatorFilters, storageFilters string
|
||||
var resourceParams, ownerParams, creatorParams, storageParams []interface{}
|
||||
params := []interface{}{uid, uid, publicShareType}
|
||||
|
||||
for _, f := range filters {
|
||||
switch f.Type {
|
||||
case link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID:
|
||||
if len(resourceFilters) != 0 {
|
||||
resourceFilters += " OR "
|
||||
}
|
||||
resourceFilters += "item_source=?"
|
||||
resourceParams = append(resourceParams, f.GetResourceId().GetOpaqueId())
|
||||
case link.ListPublicSharesRequest_Filter_TYPE_OWNER:
|
||||
if len(ownerFilters) != 0 {
|
||||
ownerFilters += " OR "
|
||||
}
|
||||
ownerFilters += "(uid_owner=?)"
|
||||
ownerParams = append(ownerParams, formatUserID(f.GetOwner()))
|
||||
case link.ListPublicSharesRequest_Filter_TYPE_CREATOR:
|
||||
if len(creatorFilters) != 0 {
|
||||
creatorFilters += " OR "
|
||||
}
|
||||
creatorFilters += "(uid_initiator=?)"
|
||||
creatorParams = append(creatorParams, formatUserID(f.GetCreator()))
|
||||
case publicshare.StorageIDFilterType:
|
||||
if len(storageFilters) != 0 {
|
||||
storageFilters += " OR "
|
||||
}
|
||||
storageFilters += "(storage=?)"
|
||||
storageParams = append(storageParams, f.GetResourceId().GetStorageId())
|
||||
}
|
||||
}
|
||||
if resourceFilters != "" {
|
||||
query = fmt.Sprintf("%s AND (%s)", query, resourceFilters)
|
||||
params = append(params, resourceParams...)
|
||||
}
|
||||
if ownerFilters != "" {
|
||||
query = fmt.Sprintf("%s AND (%s)", query, ownerFilters)
|
||||
params = append(params, ownerParams...)
|
||||
}
|
||||
if creatorFilters != "" {
|
||||
query = fmt.Sprintf("%s AND (%s)", query, creatorFilters)
|
||||
params = append(params, creatorParams...)
|
||||
}
|
||||
if storageFilters != "" {
|
||||
query = fmt.Sprintf("%s AND (%s)", query, storageFilters)
|
||||
params = append(params, storageParams...)
|
||||
}
|
||||
|
||||
rows, err := m.db.Query(query, params...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var s DBShare
|
||||
shares := []*link.PublicShare{}
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(&s.UIDOwner, &s.UIDInitiator, &s.ShareWith, &s.FileSource, &s.ItemType, &s.Token, &s.Expiration, &s.ShareName, &s.ID, &s.STime, &s.Permissions, &s.ItemStorage); err != nil {
|
||||
continue
|
||||
}
|
||||
var cs3Share *link.PublicShare
|
||||
if cs3Share, err = m.ConvertToCS3PublicShare(ctx, s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if publicshare.IsExpired(cs3Share) {
|
||||
_ = m.cleanupExpiredShares()
|
||||
} else {
|
||||
if cs3Share.PasswordProtected && sign {
|
||||
if err := publicshare.AddSignature(cs3Share, strings.TrimPrefix(s.ShareWith, "1|")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
shares = append(shares, cs3Share)
|
||||
}
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return shares, nil
|
||||
}
|
||||
|
||||
func (m *mgr) RevokePublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference) error {
|
||||
uid := u.Username
|
||||
query := "delete from oc_share where "
|
||||
params := []interface{}{}
|
||||
|
||||
switch {
|
||||
case ref.GetId() != nil && ref.GetId().OpaqueId != "":
|
||||
query += "id=? AND (uid_owner=? or uid_initiator=?)"
|
||||
params = append(params, ref.GetId().OpaqueId, uid, uid)
|
||||
case ref.GetToken() != "":
|
||||
query += "token=? AND (uid_owner=? or uid_initiator=?)"
|
||||
params = append(params, ref.GetToken(), uid, uid)
|
||||
default:
|
||||
return errtypes.NotFound(ref.String())
|
||||
}
|
||||
|
||||
stmt, err := m.db.Prepare(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := stmt.Exec(params...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rowCnt, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rowCnt == 0 {
|
||||
return errtypes.NotFound(ref.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mgr) GetPublicShareByToken(ctx context.Context, token string, auth *link.PublicShareAuthentication, sign bool) (*link.PublicShare, error) {
|
||||
ps, err := m.getByToken(ctx, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if publicshare.IsExpired(&ps.PublicShare) {
|
||||
if err := m.cleanupExpiredShares(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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 (m *mgr) cleanupExpiredShares() error {
|
||||
if !m.c.EnableExpiredSharesCleanup {
|
||||
return nil
|
||||
}
|
||||
|
||||
query := "DELETE FROM oc_share WHERE expiration IS NOT NULL AND expiration < ?"
|
||||
params := []interface{}{time.Now().Format("2006-01-02 03:04:05")}
|
||||
|
||||
stmt, err := m.db.Prepare(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = stmt.Exec(params...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Generated
Vendored
BIN
Binary file not shown.
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
// 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 registry
|
||||
|
||||
import "github.com/opencloud-eu/reva/v2/pkg/publicshare"
|
||||
|
||||
// NewFunc is the function that share managers
|
||||
// should register at init time.
|
||||
type NewFunc func(map[string]interface{}) (publicshare.Manager, error)
|
||||
|
||||
// NewFuncs is a map containing all the registered share managers.
|
||||
var NewFuncs = map[string]NewFunc{}
|
||||
|
||||
// Register registers a new share manager new function.
|
||||
// Not safe for concurrent use. Safe for use from package init.
|
||||
func Register(name string, f NewFunc) {
|
||||
NewFuncs[name] = f
|
||||
}
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
// 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 publicshare
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"time"
|
||||
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const (
|
||||
// StorageIDFilterType defines a new filter type for storage id.
|
||||
// TODO: Remove this once the filter type is in the CS3 API.
|
||||
StorageIDFilterType link.ListPublicSharesRequest_Filter_Type = 4
|
||||
)
|
||||
|
||||
// Manager manipulates public shares.
|
||||
type Manager interface {
|
||||
CreatePublicShare(ctx context.Context, u *user.User, md *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error)
|
||||
UpdatePublicShare(ctx context.Context, u *user.User, req *link.UpdatePublicShareRequest) (*link.PublicShare, error)
|
||||
GetPublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference, sign bool) (*link.PublicShare, error)
|
||||
ListPublicShares(ctx context.Context, u *user.User, filters []*link.ListPublicSharesRequest_Filter, sign bool) ([]*link.PublicShare, error)
|
||||
RevokePublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference) error
|
||||
GetPublicShareByToken(ctx context.Context, token string, auth *link.PublicShareAuthentication, sign bool) (*link.PublicShare, error)
|
||||
}
|
||||
|
||||
// WithPassword holds the relevant information for representing a public share
|
||||
type WithPassword struct {
|
||||
Password string `json:"password"`
|
||||
PublicShare link.PublicShare
|
||||
}
|
||||
|
||||
// DumpableManager defines a share manager which supports dumping its contents
|
||||
type DumpableManager interface {
|
||||
Dump(ctx context.Context, shareChan chan<- *WithPassword) error
|
||||
}
|
||||
|
||||
// LoadableManager defines a share manager which supports loading contents from a dump
|
||||
type LoadableManager interface {
|
||||
Load(ctx context.Context, shareChan <-chan *WithPassword) error
|
||||
}
|
||||
|
||||
// CreateSignature calculates a signature for a public share.
|
||||
func CreateSignature(token, pw string, expiration time.Time) (string, error) {
|
||||
h := sha256.New()
|
||||
_, err := h.Write([]byte(pw))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
key := make([]byte, 0, 32)
|
||||
key = h.Sum(key)
|
||||
|
||||
mac := hmac.New(sha512.New512_256, key)
|
||||
_, err = mac.Write([]byte(token + "|" + expiration.Format(time.RFC3339)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
sig := make([]byte, 0, 32)
|
||||
sig = mac.Sum(sig)
|
||||
|
||||
return hex.EncodeToString(sig), nil
|
||||
}
|
||||
|
||||
// AddSignature augments a public share with a signature.
|
||||
// The signature has a validity of 30 minutes.
|
||||
func AddSignature(share *link.PublicShare, pw string) error {
|
||||
expiration := time.Now().Add(time.Minute * 30)
|
||||
sig, err := CreateSignature(share.Token, pw, expiration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
share.Signature = &link.ShareSignature{
|
||||
Signature: sig,
|
||||
SignatureExpiration: &typesv1beta1.Timestamp{
|
||||
Seconds: uint64(expiration.UnixNano() / 1000000000),
|
||||
Nanos: uint32(expiration.UnixNano() % 1000000000),
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResourceIDFilter is an abstraction for creating filter by resource id.
|
||||
func ResourceIDFilter(id *provider.ResourceId) *link.ListPublicSharesRequest_Filter {
|
||||
return &link.ListPublicSharesRequest_Filter{
|
||||
Type: link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID,
|
||||
Term: &link.ListPublicSharesRequest_Filter_ResourceId{
|
||||
ResourceId: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// StorageIDFilter is an abstraction for creating filter by storage id.
|
||||
func StorageIDFilter(id string) *link.ListPublicSharesRequest_Filter {
|
||||
return &link.ListPublicSharesRequest_Filter{
|
||||
Type: StorageIDFilterType,
|
||||
Term: &link.ListPublicSharesRequest_Filter_ResourceId{
|
||||
ResourceId: &provider.ResourceId{
|
||||
StorageId: id,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MatchesFilter tests if the share passes the filter.
|
||||
func MatchesFilter(share *link.PublicShare, filter *link.ListPublicSharesRequest_Filter) bool {
|
||||
switch filter.Type {
|
||||
case link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID:
|
||||
return utils.ResourceIDEqual(share.ResourceId, filter.GetResourceId())
|
||||
case StorageIDFilterType:
|
||||
return share.ResourceId.StorageId == filter.GetResourceId().GetStorageId()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MatchesAnyFilter checks if the share passes at least one of the given filters.
|
||||
func MatchesAnyFilter(share *link.PublicShare, filters []*link.ListPublicSharesRequest_Filter) bool {
|
||||
for _, f := range filters {
|
||||
if MatchesFilter(share, f) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MatchesFilters checks if the share passes the given filters.
|
||||
// Filters of the same type form a disjuntion, a logical OR. Filters of separate type form a conjunction, a logical AND.
|
||||
// Here is an example:
|
||||
// (resource_id=1 OR resource_id=2) AND (grantee_type=USER OR grantee_type=GROUP)
|
||||
func MatchesFilters(share *link.PublicShare, filters []*link.ListPublicSharesRequest_Filter) bool {
|
||||
if len(filters) == 0 {
|
||||
return true
|
||||
}
|
||||
grouped := GroupFiltersByType(filters)
|
||||
for _, f := range grouped {
|
||||
if !MatchesAnyFilter(share, f) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GroupFiltersByType groups the given filters and returns a map using the filter type as the key.
|
||||
func GroupFiltersByType(filters []*link.ListPublicSharesRequest_Filter) map[link.ListPublicSharesRequest_Filter_Type][]*link.ListPublicSharesRequest_Filter {
|
||||
grouped := make(map[link.ListPublicSharesRequest_Filter_Type][]*link.ListPublicSharesRequest_Filter)
|
||||
for _, f := range filters {
|
||||
grouped[f.Type] = append(grouped[f.Type], f)
|
||||
}
|
||||
return grouped
|
||||
}
|
||||
|
||||
// IsExpired tests whether a public share is expired
|
||||
func IsExpired(s *link.PublicShare) bool {
|
||||
expiration := time.Unix(int64(s.Expiration.GetSeconds()), int64(s.Expiration.GetNanos()))
|
||||
return s.Expiration != nil && expiration.Before(time.Now())
|
||||
}
|
||||
|
||||
// Authenticate checks the signature or password authentication for a public share
|
||||
func Authenticate(share *link.PublicShare, pw string, auth *link.PublicShareAuthentication) bool {
|
||||
switch {
|
||||
case auth.GetPassword() != "":
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(pw), []byte(auth.GetPassword())); err == nil {
|
||||
return true
|
||||
}
|
||||
case auth.GetSignature() != nil:
|
||||
sig := auth.GetSignature()
|
||||
now := time.Now()
|
||||
expiration := time.Unix(int64(sig.GetSignatureExpiration().GetSeconds()), int64(sig.GetSignatureExpiration().GetNanos()))
|
||||
if now.After(expiration) {
|
||||
return false
|
||||
}
|
||||
s, err := CreateSignature(share.Token, pw, expiration)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return sig.GetSignature() == s
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCreatedByUser checks if a share was created by the user.
|
||||
func IsCreatedByUser(share *link.PublicShare, user *user.User) bool {
|
||||
return utils.UserEqual(user.Id, share.Owner) || utils.UserEqual(user.Id, share.Creator)
|
||||
}
|
||||
|
||||
// IsWriteable checks if the grant for a publicshare allows writes or uploads.
|
||||
func IsWriteable(perm *link.PublicSharePermissions) bool {
|
||||
p := perm.GetPermissions()
|
||||
return p != nil && (p.CreateContainer || p.Delete || p.InitiateFileUpload ||
|
||||
p.Move || p.AddGrant || p.PurgeRecycle || p.RestoreFileVersion || p.RestoreRecycleItem)
|
||||
}
|
||||
Reference in New Issue
Block a user