Initial QSfera import
This commit is contained in:
Generated
Vendored
+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.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
|
||||
}
|
||||
Generated
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
// 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/json"
|
||||
_ "github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/memory"
|
||||
// Add your own here
|
||||
)
|
||||
Generated
Vendored
+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
+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