Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[full-ci] Allow no permissions on Links #2687

Merged
merged 16 commits into from
Apr 4, 2022
9 changes: 8 additions & 1 deletion internal/http/services/owncloud/ocdav/dav.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type DavHandler struct {
MetaHandler *MetaHandler
TrashbinHandler *TrashbinHandler
SpacesHandler *SpacesHandler
TokenHandler *TokenHandler
PublicFolderHandler *WebDavHandler
PublicFileHandler *PublicFileHandler
SharesHandler *WebDavHandler
Expand All @@ -75,6 +76,9 @@ func (h *DavHandler) init(c *Config) error {
return err
}
h.TrashbinHandler = new(TrashbinHandler)
if err := h.TrashbinHandler.init(c); err != nil {
return err
}

h.SpacesHandler = new(SpacesHandler)
if err := h.SpacesHandler.init(c); err != nil {
Expand All @@ -91,7 +95,8 @@ func (h *DavHandler) init(c *Config) error {
return err
}

return h.TrashbinHandler.init(c)
h.TokenHandler = new(TokenHandler)
return nil
}

func isOwner(userIDorName string, user *userv1beta1.User) bool {
Expand Down Expand Up @@ -135,6 +140,8 @@ func (h *DavHandler) Handler(s *svc) http.Handler {
head, r.URL.Path = router.ShiftPath(r.URL.Path)

switch head {
case "tokeninfo":
h.TokenHandler.Handler(s).ServeHTTP(w, r)
case "avatars":
h.AvatarsHandler.Handler(s).ServeHTTP(w, r)
case "files":
Expand Down
2 changes: 1 addition & 1 deletion internal/http/services/owncloud/ocdav/ocdav.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func (s *svc) Close() error {
}

func (s *svc) Unprotected() []string {
return []string{"/status.php", "/remote.php/dav/public-files/", "/apps/files/", "/index.php/f/", "/index.php/s/"}
return []string{"/status.php", "/remote.php/dav/public-files/", "/remote.php/dav/tokeninfo/unprotected", "/apps/files/", "/index.php/f/", "/index.php/s/"}
}

func (s *svc) Handler() http.Handler {
Expand Down
171 changes: 171 additions & 0 deletions internal/http/services/owncloud/ocdav/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package ocdav

import (
"context"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"net/http"
"net/url"
"path"

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"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup"
"github.com/cs3org/reva/v2/pkg/appctx"
ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
"github.com/cs3org/reva/v2/pkg/rhttp/router"
"github.com/cs3org/reva/v2/pkg/utils"
"google.golang.org/grpc/metadata"
)

// TokenHandler handles requests for public link tokens
type TokenHandler struct{}

// Handler handles http requests
func (t TokenHandler) Handler(s *svc) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}

typ, tkn := router.ShiftPath(r.URL.Path)
tkn, _ = router.ShiftPath(tkn)

c, err := pool.GetGatewayServiceClient(s.c.GatewaySvc)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

switch typ {
case "protected":
s.handleGetToken(w, r, tkn, c, true)
case "unprotected":
s.handleGetToken(w, r, tkn, c, false)
}
})
}

// TokenInfo contains information about the token
type TokenInfo struct {
// for all callers
Token string `xml:"token"`
LinkURL string `xml:"linkurl"`
PasswordProtected bool `xml:"passwordprotected"`

// if not password protected
StorageID string `xml:"storageid"`
OpaqueID string `xml:"opaqueid"`
Path string `xml:"path"`

// if native access
SpacePath string `xml:"spacePath"`
SpaceAlias string `xml:"spaceAlias"`
SpaceURL string `xml:"spaceURL"`
}
kobergj marked this conversation as resolved.
Show resolved Hide resolved

func (s *svc) handleGetToken(w http.ResponseWriter, r *http.Request, tkn string, c gateway.GatewayAPIClient, protected bool) {
ctx := r.Context()
log := appctx.GetLogger(ctx)

user, token, passwordProtected, err := getInfoForToken(tkn, r.URL.Query(), c)
if err != nil {
log.Error().Err(err).Msg("error stating token")
w.WriteHeader(http.StatusInternalServerError)
return
}

t, err := buildTokenInfo(user, tkn, token, passwordProtected, c)
if err != nil {
log.Error().Err(err).Msg("error stating resource behind token")
w.WriteHeader(http.StatusInternalServerError)
return
}

if protected {
if t.PasswordProtected == true {
log.Error().Msg("password protected private links are not supported")
w.WriteHeader(http.StatusBadRequest)
return
}
space, status, err := spacelookup.LookUpStorageSpaceByID(ctx, c, t.StorageID)
// add info only if user is able to stat
if err == nil && status.Code == rpc.Code_CODE_OK {
t.SpacePath = utils.ReadPlainFromOpaque(space.Opaque, "path")
t.SpaceAlias = utils.ReadPlainFromOpaque(space.Opaque, "spaceAlias")
t.SpaceURL = path.Join(t.SpaceAlias, t.OpaqueID, t.Path)
}

}

b, err := xml.Marshal(t)
if err != nil {
log.Error().Err(err).Msg("error marshaling xml")
w.WriteHeader(http.StatusInternalServerError)
return
}

w.Write(b)
w.WriteHeader(http.StatusOK)
return
}

func buildTokenInfo(owner *user.User, tkn string, token string, passProtected bool, c gateway.GatewayAPIClient) (TokenInfo, error) {
t := TokenInfo{Token: tkn, LinkURL: "/s/" + tkn}
if passProtected {
t.PasswordProtected = true
return t, nil
}

ctx := ctxpkg.ContextSetToken(context.TODO(), token)
ctx = ctxpkg.ContextSetUser(ctx, owner)
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, token)

sRes, err := getTokenStatInfo(ctx, c, tkn)
if err != nil || sRes.Status.Code != rpc.Code_CODE_OK {
return t, fmt.Errorf("can't stat resource. %+v %s", sRes, err)
}

ls := &link.PublicShare{}
_ = json.Unmarshal(sRes.Info.Opaque.Map["link-share"].Value, ls)

t.StorageID = ls.ResourceId.GetStorageId()
t.OpaqueID = ls.ResourceId.GetOpaqueId()

return t, nil
}

func getInfoForToken(tkn string, q url.Values, c gateway.GatewayAPIClient) (owner *user.User, token string, passwordProtected bool, err error) {
ctx := context.Background()

sig := q.Get("signature")
kobergj marked this conversation as resolved.
Show resolved Hide resolved
expiration := q.Get("expiration")
res, err := handleSignatureAuth(ctx, c, tkn, sig, expiration)
if err != nil {
return
}

switch res.Status.Code {
case rpc.Code_CODE_OK:
// nothing to do
case rpc.Code_CODE_PERMISSION_DENIED:
if res.Status.Message != "wrong password" {
err = errors.New("not found")
return
}

passwordProtected = true
return
default:
err = fmt.Errorf("authentication returned unsupported status code '%d'", res.Status.Code)
return
}

return res.User, res.Token, false, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ var (
// The value must be in the valid range.
func NewPermissions(val int) (Permissions, error) {
if val == int(PermissionInvalid) {
return PermissionInvalid, fmt.Errorf("permissions %d out of range %d - %d", val, PermissionRead, PermissionAll)
return PermissionInvalid, nil //fmt.Errorf("permissions %d out of range %d - %d", val, PermissionRead, PermissionAll)
} else if val < int(PermissionInvalid) || int(PermissionAll) < val {
return PermissionInvalid, ErrPermissionNotInRange
}
Expand Down
13 changes: 13 additions & 0 deletions internal/http/services/owncloud/ocs/conversions/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@ func NewUploaderRole() *Role {
}
}

// NewNoneRole creates a role with no permissions
func NewNoneRole() *Role {
return &Role{
Name: "none",
cS3ResourcePermissions: &provider.ResourcePermissions{},
ocsPermissions: PermissionInvalid,
}
}

// NewManagerRole creates an manager role
func NewManagerRole() *Role {
return &Role{
Expand Down Expand Up @@ -254,6 +263,10 @@ func NewManagerRole() *Role {

// RoleFromOCSPermissions tries to map ocs permissions to a role
func RoleFromOCSPermissions(p Permissions) *Role {
if p == PermissionInvalid {
return NewNoneRole()
}

if p.Contain(PermissionRead) {
if p.Contain(PermissionWrite) && p.Contain(PermissionCreate) && p.Contain(PermissionDelete) {
if p.Contain(PermissionShare) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,8 @@ func permissionFromRequest(r *http.Request, h *Handler) (*provider.ResourcePermi

// Maps oc10 public link permissions to roles
var ocPublicPermToRole = map[int]string{
// Recipients can do nothing
0: "none",
// Recipients can view and download contents.
1: "viewer",
// Recipients can view, download and edit single files.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -430,17 +430,19 @@ func (h *Handler) extractPermissions(w http.ResponseWriter, r *http.Request, ri
// Single file shares should never have delete or create permissions
permissions &^= conversions.PermissionCreate
permissions &^= conversions.PermissionDelete
if permissions == conversions.PermissionInvalid {
return nil, nil, &ocsError{
Code: response.MetaBadRequest.StatusCode,
Message: "Cannot set the requested share permissions",
Error: errors.New("cannot set the requested share permissions"),
/*
if permissions == conversions.PermissionInvalid {
return nil, nil, &ocsError{
Code: response.MetaBadRequest.StatusCode,
Message: "Cannot set the requested share permissions",
Error: errors.New("cannot set the requested share permissions"),
}
}
}
*/
}

existingPermissions := conversions.RoleFromResourcePermissions(ri.PermissionSet).OCSPermissions()
if permissions == conversions.PermissionInvalid || !existingPermissions.Contain(permissions) {
if !existingPermissions.Contain(permissions) {
return nil, nil, &ocsError{
Code: http.StatusNotFound,
Message: "Cannot set the requested share permissions",
Expand Down
9 changes: 9 additions & 0 deletions tests/acceptance/expected-failures-on-OCIS-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -1576,5 +1576,14 @@ _ocs: api compatibility, return correct status code_

- [apiMain/checksums.feature:233](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiMain/checksums.feature#L233)

#### empty permissions on a link is now allowed

- [apiShareUpdateToShares/updateShare.feature:112](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L112)
- [apiShareUpdateToShares/updateShare.feature:113](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareUpdateToShares/updateShare.feature#L113)
- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:116](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L116)
- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:117](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L117)
- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:131](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L131)
- [apiShareManagementBasicToShares/createShareToSharesFolder.feature:132](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareManagementBasicToShares/createShareToSharesFolder.feature#L132)

Note: always have an empty line at the end of this file.
The bash script that processes this file may not process a scenario reference on the last line.