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

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package publicshare
    20  
    21  import (
    22  	"context"
    23  	"crypto/hmac"
    24  	"crypto/sha256"
    25  	"crypto/sha512"
    26  	"encoding/hex"
    27  	"time"
    28  
    29  	user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    30  	link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
    31  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    32  	typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    33  	"github.com/cs3org/reva/v2/pkg/utils"
    34  	"golang.org/x/crypto/bcrypt"
    35  )
    36  
    37  const (
    38  	// StorageIDFilterType defines a new filter type for storage id.
    39  	// TODO: Remove this once the filter type is in the CS3 API.
    40  	StorageIDFilterType link.ListPublicSharesRequest_Filter_Type = 4
    41  )
    42  
    43  // Manager manipulates public shares.
    44  type Manager interface {
    45  	CreatePublicShare(ctx context.Context, u *user.User, md *provider.ResourceInfo, g *link.Grant) (*link.PublicShare, error)
    46  	UpdatePublicShare(ctx context.Context, u *user.User, req *link.UpdatePublicShareRequest) (*link.PublicShare, error)
    47  	GetPublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference, sign bool) (*link.PublicShare, error)
    48  	ListPublicShares(ctx context.Context, u *user.User, filters []*link.ListPublicSharesRequest_Filter, sign bool) ([]*link.PublicShare, error)
    49  	RevokePublicShare(ctx context.Context, u *user.User, ref *link.PublicShareReference) error
    50  	GetPublicShareByToken(ctx context.Context, token string, auth *link.PublicShareAuthentication, sign bool) (*link.PublicShare, error)
    51  }
    52  
    53  // WithPassword holds the relevant information for representing a public share
    54  type WithPassword struct {
    55  	Password    string `json:"password"`
    56  	PublicShare link.PublicShare
    57  }
    58  
    59  // DumpableManager defines a share manager which supports dumping its contents
    60  type DumpableManager interface {
    61  	Dump(ctx context.Context, shareChan chan<- *WithPassword) error
    62  }
    63  
    64  // LoadableManager defines a share manager which supports loading contents from a dump
    65  type LoadableManager interface {
    66  	Load(ctx context.Context, shareChan <-chan *WithPassword) error
    67  }
    68  
    69  // CreateSignature calculates a signature for a public share.
    70  func CreateSignature(token, pw string, expiration time.Time) (string, error) {
    71  	h := sha256.New()
    72  	_, err := h.Write([]byte(pw))
    73  	if err != nil {
    74  		return "", err
    75  	}
    76  
    77  	key := make([]byte, 0, 32)
    78  	key = h.Sum(key)
    79  
    80  	mac := hmac.New(sha512.New512_256, key)
    81  	_, err = mac.Write([]byte(token + "|" + expiration.Format(time.RFC3339)))
    82  	if err != nil {
    83  		return "", err
    84  	}
    85  
    86  	sig := make([]byte, 0, 32)
    87  	sig = mac.Sum(sig)
    88  
    89  	return hex.EncodeToString(sig), nil
    90  }
    91  
    92  // AddSignature augments a public share with a signature.
    93  // The signature has a validity of 30 minutes.
    94  func AddSignature(share *link.PublicShare, pw string) error {
    95  	expiration := time.Now().Add(time.Minute * 30)
    96  	sig, err := CreateSignature(share.Token, pw, expiration)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	share.Signature = &link.ShareSignature{
   102  		Signature: sig,
   103  		SignatureExpiration: &typesv1beta1.Timestamp{
   104  			Seconds: uint64(expiration.UnixNano() / 1000000000),
   105  			Nanos:   uint32(expiration.UnixNano() % 1000000000),
   106  		},
   107  	}
   108  	return nil
   109  }
   110  
   111  // ResourceIDFilter is an abstraction for creating filter by resource id.
   112  func ResourceIDFilter(id *provider.ResourceId) *link.ListPublicSharesRequest_Filter {
   113  	return &link.ListPublicSharesRequest_Filter{
   114  		Type: link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID,
   115  		Term: &link.ListPublicSharesRequest_Filter_ResourceId{
   116  			ResourceId: id,
   117  		},
   118  	}
   119  }
   120  
   121  // StorageIDFilter is an abstraction for creating filter by storage id.
   122  func StorageIDFilter(id string) *link.ListPublicSharesRequest_Filter {
   123  	return &link.ListPublicSharesRequest_Filter{
   124  		Type: StorageIDFilterType,
   125  		Term: &link.ListPublicSharesRequest_Filter_ResourceId{
   126  			ResourceId: &provider.ResourceId{
   127  				StorageId: id,
   128  			},
   129  		},
   130  	}
   131  }
   132  
   133  // MatchesFilter tests if the share passes the filter.
   134  func MatchesFilter(share *link.PublicShare, filter *link.ListPublicSharesRequest_Filter) bool {
   135  	switch filter.Type {
   136  	case link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID:
   137  		return utils.ResourceIDEqual(share.ResourceId, filter.GetResourceId())
   138  	case StorageIDFilterType:
   139  		return share.ResourceId.StorageId == filter.GetResourceId().GetStorageId()
   140  	default:
   141  		return false
   142  	}
   143  }
   144  
   145  // MatchesAnyFilter checks if the share passes at least one of the given filters.
   146  func MatchesAnyFilter(share *link.PublicShare, filters []*link.ListPublicSharesRequest_Filter) bool {
   147  	for _, f := range filters {
   148  		if MatchesFilter(share, f) {
   149  			return true
   150  		}
   151  	}
   152  	return false
   153  }
   154  
   155  // MatchesFilters checks if the share passes the given filters.
   156  // Filters of the same type form a disjuntion, a logical OR. Filters of separate type form a conjunction, a logical AND.
   157  // Here is an example:
   158  // (resource_id=1 OR resource_id=2) AND (grantee_type=USER OR grantee_type=GROUP)
   159  func MatchesFilters(share *link.PublicShare, filters []*link.ListPublicSharesRequest_Filter) bool {
   160  	if len(filters) == 0 {
   161  		return true
   162  	}
   163  	grouped := GroupFiltersByType(filters)
   164  	for _, f := range grouped {
   165  		if !MatchesAnyFilter(share, f) {
   166  			return false
   167  		}
   168  	}
   169  	return true
   170  }
   171  
   172  // GroupFiltersByType groups the given filters and returns a map using the filter type as the key.
   173  func GroupFiltersByType(filters []*link.ListPublicSharesRequest_Filter) map[link.ListPublicSharesRequest_Filter_Type][]*link.ListPublicSharesRequest_Filter {
   174  	grouped := make(map[link.ListPublicSharesRequest_Filter_Type][]*link.ListPublicSharesRequest_Filter)
   175  	for _, f := range filters {
   176  		grouped[f.Type] = append(grouped[f.Type], f)
   177  	}
   178  	return grouped
   179  }
   180  
   181  // IsExpired tests whether a public share is expired
   182  func IsExpired(s *link.PublicShare) bool {
   183  	expiration := time.Unix(int64(s.Expiration.GetSeconds()), int64(s.Expiration.GetNanos()))
   184  	return s.Expiration != nil && expiration.Before(time.Now())
   185  }
   186  
   187  // Authenticate checks the signature or password authentication for a public share
   188  func Authenticate(share *link.PublicShare, pw string, auth *link.PublicShareAuthentication) bool {
   189  	switch {
   190  	case auth.GetPassword() != "":
   191  		if err := bcrypt.CompareHashAndPassword([]byte(pw), []byte(auth.GetPassword())); err == nil {
   192  			return true
   193  		}
   194  	case auth.GetSignature() != nil:
   195  		sig := auth.GetSignature()
   196  		now := time.Now()
   197  		expiration := time.Unix(int64(sig.GetSignatureExpiration().GetSeconds()), int64(sig.GetSignatureExpiration().GetNanos()))
   198  		if now.After(expiration) {
   199  			return false
   200  		}
   201  		s, err := CreateSignature(share.Token, pw, expiration)
   202  		if err != nil {
   203  			return false
   204  		}
   205  		return sig.GetSignature() == s
   206  	}
   207  	return false
   208  }
   209  
   210  // IsCreatedByUser checks if a share was created by the user.
   211  func IsCreatedByUser(share *link.PublicShare, user *user.User) bool {
   212  	return utils.UserEqual(user.Id, share.Owner) || utils.UserEqual(user.Id, share.Creator)
   213  }
   214  
   215  // IsWriteable checks if the grant for a publicshare allows writes or uploads.
   216  func IsWriteable(perm *link.PublicSharePermissions) bool {
   217  	p := perm.GetPermissions()
   218  	return p != nil && (p.CreateContainer || p.Delete || p.InitiateFileUpload ||
   219  		p.Move || p.AddGrant || p.PurgeRecycle || p.RestoreFileVersion || p.RestoreRecycleItem)
   220  }