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
8 changes: 8 additions & 0 deletions changelog/unreleased/alias-links.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Change: allow link with no or edit permission

Allow the creation of links with no permissions. These can be used to navigate to a file
that a user has access to.
Allow setting edit permission on single file links (create and delete are still blocked)
Introduce endpoint to get information about a given token

https://github.com/cs3org/reva/pull/2687
5 changes: 4 additions & 1 deletion internal/http/services/owncloud/ocdav/dav.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,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 +94,7 @@ func (h *DavHandler) init(c *Config) error {
return err
}

return h.TrashbinHandler.init(c)
return nil
}

func isOwner(userIDorName string, user *userv1beta1.User) bool {
Expand Down
18 changes: 18 additions & 0 deletions internal/http/services/owncloud/ocs/conversions/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,24 @@ type ShareeData struct {
Remotes []*MatchData `json:"remotes" xml:"remotes>element"`
}

// TokenInfo holds token information
type TokenInfo struct {
// for all callers
Token string `json:"token" xml:"token"`
LinkURL string `json:"link_url" xml:"link_url"`
PasswordProtected bool `json:"password_protected" xml:"password_protected"`

// if not password protected
StorageID string `json:"storage_id" xml:"storage_id"`
OpaqueID string `json:"opaque_id" xml:"opaque_id"`
Path string `json:"path" xml:"path"`

// if native access
SpacePath string `json:"space_path" xml:"space_path"`
SpaceAlias string `json:"space_alias" xml:"space_alias"`
SpaceURL string `json:"space_url" xml:"space_url"`
}

// ExactMatchesData hold exact matches
type ExactMatchesData struct {
Users []*MatchData `json:"users" xml:"users>element"`
Expand Down
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
} else if val < int(PermissionInvalid) || int(PermissionAll) < val {
return PermissionInvalid, ErrPermissionNotInRange
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func TestNewPermissions(t *testing.T) {

func TestNewPermissionsWithInvalidValueShouldFail(t *testing.T) {
vals := []int{
int(PermissionInvalid),
-1,
int(PermissionAll) + 1,
}
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
@@ -0,0 +1,166 @@
// 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 sharees

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"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"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/spacelookup"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/conversions"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/response"
"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/utils"
"google.golang.org/grpc/metadata"
)

// TokenInfo handles http requests regarding tokens
func (h *Handler) TokenInfo(protected bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {

log := appctx.GetLogger(r.Context())
tkn := path.Base(r.URL.Path)
_, pw, _ := r.BasicAuth()

c, err := pool.GetGatewayServiceClient(h.gatewayAddr)
if err != nil {
// endpoint public - don't exponse information
log.Error().Err(err).Msg("error getting gateway client")
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "", nil)
return
}

t, err := handleGetToken(r.Context(), tkn, pw, c, protected)
if err != nil {
// endpoint public - don't exponse information
log.Error().Err(err).Msg("error while handling GET TokenInfo")
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "", nil)
return
}

response.WriteOCSSuccess(w, r, t)
}
}

func handleGetToken(ctx context.Context, tkn string, pw string, c gateway.GatewayAPIClient, protected bool) (conversions.TokenInfo, error) {
user, token, passwordProtected, err := getInfoForToken(tkn, pw, c)
if err != nil {
return conversions.TokenInfo{}, err
}

t, err := buildTokenInfo(user, tkn, token, passwordProtected, c)
if err != nil {
return t, err
}

if protected && !t.PasswordProtected {
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)
}

}

return t, nil
}

func buildTokenInfo(owner *user.User, tkn string, token string, passProtected bool, c gateway.GatewayAPIClient) (conversions.TokenInfo, error) {
t := conversions.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, pw string, c gateway.GatewayAPIClient) (owner *user.User, token string, passwordProtected bool, err error) {
ctx := context.Background()

res, err := handleBasicAuth(ctx, c, tkn, pw)
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
}

func handleBasicAuth(ctx context.Context, c gateway.GatewayAPIClient, token, pw string) (*gateway.AuthenticateResponse, error) {
authenticateRequest := gateway.AuthenticateRequest{
Type: "publicshares",
ClientId: token,
ClientSecret: "password|" + pw,
}

return c.Authenticate(ctx, &authenticateRequest)
}

func getTokenStatInfo(ctx context.Context, client gateway.GatewayAPIClient, token string) (*provider.StatResponse, error) {
return client.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{
ResourceId: &provider.ResourceId{
StorageId: utils.PublicStorageProviderID,
OpaqueId: token,
},
}})
}
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 @@ -426,7 +426,7 @@ func (h *Handler) extractPermissions(w http.ResponseWriter, r *http.Request, ri
}

permissions := role.OCSPermissions()
if ri != nil && ri.Type == provider.ResourceType_RESOURCE_TYPE_FILE {
if ri != nil && ri.Type == provider.ResourceType_RESOURCE_TYPE_FILE && permissions != conversions.PermissionInvalid {
// Single file shares should never have delete or create permissions
permissions &^= conversions.PermissionCreate
permissions &^= conversions.PermissionDelete
Expand All @@ -440,7 +440,7 @@ func (h *Handler) extractPermissions(w http.ResponseWriter, r *http.Request, ri
}

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
6 changes: 6 additions & 0 deletions internal/http/services/owncloud/ocs/ocs.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ func (s *svc) Unprotected() []string {
return []string{
"/v1.php/config",
"/v2.php/config",
"/v1.php/apps/files_sharing/api/v1/tokeninfo/unprotected",
"/v2.php/apps/files_sharing/api/v1/tokeninfo/unprotected",
}
}

Expand Down Expand Up @@ -125,6 +127,10 @@ func (s *svc) routerInit() error {
r.Delete("/{shareid}", sharesHandler.RemoveShare)
})
r.Get("/sharees", shareesHandler.FindSharees)
r.Route("/tokeninfo", func(r chi.Router) {
r.Get("/protected/{tkn}", shareesHandler.TokenInfo(true))
r.Get("/unprotected/{tkn}", shareesHandler.TokenInfo(false))
})
})

// placeholder for notifications
Expand Down
13 changes: 13 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,18 @@ _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)
- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L26)
- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L27)
- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L28)
- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L29)

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.
13 changes: 13 additions & 0 deletions tests/acceptance/expected-failures-on-S3NG-storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -1576,5 +1576,18 @@ _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)
- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:26](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L26)
- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:27](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L27)
- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:28](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L28)
- [apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:29](https://github.com/owncloud/core/blob/master/tests/acceptance/features/apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature#L29)

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.