Skip to content

Commit

Permalink
Share types for public links (#2213)
Browse files Browse the repository at this point in the history
  • Loading branch information
David Christofas authored Nov 10, 2021
1 parent 316e676 commit 2ca63c8
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 32 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/public-share-share-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Add public link share type to propfind response

Added share type for public links to propfind responses.

https://github.com/cs3org/reva/pull/2213
51 changes: 45 additions & 6 deletions internal/http/services/owncloud/ocdav/propfind.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ import (
"github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions"
"github.com/cs3org/reva/pkg/appctx"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
"github.com/cs3org/reva/pkg/publicshare"
rtrace "github.com/cs3org/reva/pkg/trace"
"github.com/cs3org/reva/pkg/utils"
"github.com/rs/zerolog"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
)

const (
Expand Down Expand Up @@ -134,7 +136,34 @@ func (s *svc) handleSpacesPropfind(w http.ResponseWriter, r *http.Request, space
}

func (s *svc) propfindResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, namespace string, pf propfindXML, parentInfo *provider.ResourceInfo, resourceInfos []*provider.ResourceInfo, log zerolog.Logger) {
propRes, err := s.multistatusResponse(ctx, &pf, resourceInfos, namespace)
ctx, span := rtrace.Provider.Tracer("ocdav").Start(ctx, "propfind_response")
defer span.End()

filters := make([]*link.ListPublicSharesRequest_Filter, 0, len(resourceInfos))
for i := range resourceInfos {
filters = append(filters, publicshare.ResourceIDFilter(resourceInfos[i].Id))
}

client, err := s.getClient()
if err != nil {
log.Error().Err(err).Msg("error getting grpc client")
w.WriteHeader(http.StatusInternalServerError)
return
}

var linkshares map[string]struct{}
listResp, err := client.ListPublicShares(ctx, &link.ListPublicSharesRequest{Filters: filters})
if err == nil {
linkshares := make(map[string]struct{})
for i := range listResp.Share {
linkshares[listResp.Share[i].ResourceId.OpaqueId] = struct{}{}
}
} else {
log.Error().Err(err).Msg("propfindResponse: couldn't list public shares")
span.SetStatus(codes.Error, err.Error())
}

propRes, err := s.multistatusResponse(ctx, &pf, resourceInfos, namespace, linkshares)
if err != nil {
log.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
Expand Down Expand Up @@ -374,10 +403,10 @@ func readPropfind(r io.Reader) (pf propfindXML, status int, err error) {
return pf, 0, nil
}

func (s *svc) multistatusResponse(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string) (string, error) {
func (s *svc) multistatusResponse(ctx context.Context, pf *propfindXML, mds []*provider.ResourceInfo, ns string, linkshares map[string]struct{}) (string, error) {
responses := make([]*responseXML, 0, len(mds))
for i := range mds {
res, err := s.mdToPropResponse(ctx, pf, mds[i], ns)
res, err := s.mdToPropResponse(ctx, pf, mds[i], ns, linkshares)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -429,7 +458,7 @@ func (s *svc) newPropRaw(key, val string) *propertyXML {
// mdToPropResponse converts the CS3 metadata into a webdav PropResponse
// ns is the CS3 namespace that needs to be removed from the CS3 path before
// prefixing it with the baseURI
func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provider.ResourceInfo, ns string) (*responseXML, error) {
func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provider.ResourceInfo, ns string, linkshares map[string]struct{}) (*responseXML, error) {
sublog := appctx.GetLogger(ctx).With().Interface("md", md).Str("ns", ns).Logger()
md.Path = strings.TrimPrefix(md.Path, ns)

Expand Down Expand Up @@ -739,11 +768,21 @@ func (s *svc) mdToPropResponse(ctx context.Context, pf *propfindXML, md *provide
propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:checksums", ""))
}
case "share-types": // desktop
var types strings.Builder
k := md.GetArbitraryMetadata()
amd := k.GetMetadata()
if amdv, ok := amd[metadataKeyOf(&pf.Prop[i])]; ok {
st := fmt.Sprintf("<oc:share-type>%s</oc:share-type>", amdv)
propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:share-types", st))
types.WriteString("<oc:share-type>")
types.WriteString(amdv)
types.WriteString("</oc:share-type>")
}

if _, ok := linkshares[md.Id.OpaqueId]; ok {
types.WriteString("<oc:share-type>3</oc:share-type>")
}

if types.Len() != 0 {
propstatOK.Prop = append(propstatOK.Prop, s.newPropRaw("oc:share-types", types.String()))
} else {
propstatNotFound.Prop = append(propstatNotFound.Prop, s.newProp("oc:"+pf.Prop[i].Local, ""))
}
Expand Down
2 changes: 1 addition & 1 deletion internal/http/services/owncloud/ocdav/publicfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func (s *svc) handlePropfindOnToken(w http.ResponseWriter, r *http.Request, ns s

infos := s.getPublicFileInfos(onContainer, depth == "0", tokenStatInfo)

propRes, err := s.multistatusResponse(ctx, &pf, infos, ns)
propRes, err := s.multistatusResponse(ctx, &pf, infos, ns, nil)
if err != nil {
sublog.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
Expand Down
2 changes: 1 addition & 1 deletion internal/http/services/owncloud/ocdav/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (s *svc) doFilterFiles(w http.ResponseWriter, r *http.Request, ff *reportFi
infos = append(infos, statRes.Info)
}

responsesXML, err := s.multistatusResponse(ctx, &propfindXML{Prop: ff.Prop}, infos, namespace)
responsesXML, err := s.multistatusResponse(ctx, &propfindXML{Prop: ff.Prop}, infos, namespace, nil)
if err != nil {
log.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
Expand Down
2 changes: 1 addition & 1 deletion internal/http/services/owncloud/ocdav/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func (h *VersionsHandler) doListVersions(w http.ResponseWriter, r *http.Request,
infos = append(infos, vi)
}

propRes, err := s.multistatusResponse(ctx, &pf, infos, "")
propRes, err := s.multistatusResponse(ctx, &pf, infos, "", nil)
if err != nil {
sublog.Error().Err(err).Msg("error formatting propfind")
w.WriteHeader(http.StatusInternalServerError)
Expand Down
33 changes: 10 additions & 23 deletions pkg/publicshare/manager/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ func (m *manager) GetPublicShare(ctx context.Context, u *user.User, ref *link.Pu
}

if ref.GetId().GetOpaqueId() == ps.Id.OpaqueId {
if !notExpired(&ps) {
if publicshare.IsExpired(&ps) {
if err := m.revokeExpiredPublicShare(ctx, &ps, u); err != nil {
return nil, err
}
Expand Down Expand Up @@ -383,34 +383,21 @@ func (m *manager) ListPublicShares(ctx context.Context, u *user.User, filters []

if len(filters) == 0 {
shares = append(shares, &local.PublicShare)
} else {
for i := range filters {
if filters[i].Type == link.ListPublicSharesRequest_Filter_TYPE_RESOURCE_ID {
if utils.ResourceIDEqual(local.ResourceId, filters[i].GetResourceId()) {
if notExpired(&local.PublicShare) {
shares = append(shares, &local.PublicShare)
} else if err := m.revokeExpiredPublicShare(ctx, &local.PublicShare, u); err != nil {
return nil, err
}
}
continue
}

}
if publicshare.MatchesFilters(&local.PublicShare, filters) {
if !publicshare.IsExpired(&local.PublicShare) {
shares = append(shares, &local.PublicShare)
} else if err := m.revokeExpiredPublicShare(ctx, &local.PublicShare, u); err != nil {
return nil, err
}
}
}

return shares, nil
}

// notExpired tests whether a public share is expired
func notExpired(s *link.PublicShare) bool {
t := time.Unix(int64(s.Expiration.GetSeconds()), int64(s.Expiration.GetNanos()))
if (s.Expiration != nil && t.After(time.Now())) || s.Expiration == nil {
return true
}
return false
}

func (m *manager) cleanupExpiredShares() {
m.mutex.Lock()
defer m.mutex.Unlock()
Expand All @@ -423,7 +410,7 @@ func (m *manager) cleanupExpiredShares() {
var ps link.PublicShare
_ = utils.UnmarshalJSONToProtoV1([]byte(d.(string)), &ps)

if !notExpired(&ps) {
if publicshare.IsExpired(&ps) {
_ = m.revokeExpiredPublicShare(context.Background(), &ps, nil)
}
}
Expand Down Expand Up @@ -525,7 +512,7 @@ func (m *manager) GetPublicShareByToken(ctx context.Context, token string, auth
}

if local.Token == token {
if !notExpired(&local) {
if publicshare.IsExpired(&local) {
// TODO user is not needed at all in this API.
if err := m.revokeExpiredPublicShare(ctx, &local, nil); err != nil {
return nil, err
Expand Down
50 changes: 50 additions & 0 deletions pkg/publicshare/publicshare.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
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/cs3org/reva/pkg/utils"
)

// Manager manipulates public shares.
Expand Down Expand Up @@ -93,3 +94,52 @@ func ResourceIDFilter(id *provider.ResourceId) *link.ListPublicSharesRequest_Fil
},
}
}

// 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())
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 {
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())
}

0 comments on commit 2ca63c8

Please sign in to comment.