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

feat(api): sharing NG links #7743

Merged
merged 13 commits into from
Nov 28, 2023
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ require (
github.com/onsi/gomega v1.29.0
github.com/open-policy-agent/opa v0.51.0
github.com/orcaman/concurrent-map v1.0.0
github.com/owncloud/libre-graph-api-go v1.0.5-0.20231113143725-09bf34dc9afb
github.com/owncloud/libre-graph-api-go v1.0.5-0.20231128074031-fdcdb2371356
github.com/pkg/errors v0.9.1
github.com/pkg/xattr v0.4.9
github.com/prometheus/client_golang v1.17.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1789,8 +1789,8 @@ github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35uk
github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
github.com/owncloud/libre-graph-api-go v1.0.5-0.20231113143725-09bf34dc9afb h1:KFnmkGvHY+6k6IZ9I1w5Ia24VbALYms+Y6W7LrsUbsE=
github.com/owncloud/libre-graph-api-go v1.0.5-0.20231113143725-09bf34dc9afb/go.mod h1:v2aAl5IwEI8t+GmcWvBd+bvJMYp9Vf1hekLuRf0UnEs=
github.com/owncloud/libre-graph-api-go v1.0.5-0.20231128074031-fdcdb2371356 h1:JjjpyUlD5nKF79QMpQ7/KVq41hh6f49GJNuxCYgMIMA=
github.com/owncloud/libre-graph-api-go v1.0.5-0.20231128074031-fdcdb2371356/go.mod h1:v2aAl5IwEI8t+GmcWvBd+bvJMYp9Vf1hekLuRf0UnEs=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
Expand Down
178 changes: 178 additions & 0 deletions services/graph/pkg/linktype/linktype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package linktype

import (
"errors"

linkv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/storage/utils/grants"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/services/graph/pkg/unifiedrole"
)

// NoPermissionMatchError is the message returned by a failed conversion
const NoPermissionMatchError = "no matching permission set found"
micbar marked this conversation as resolved.
Show resolved Hide resolved

// LinkType contains cs3 permissions and a libregraph
// linktype reference
type LinkType struct {
micbar marked this conversation as resolved.
Show resolved Hide resolved
Permissions *provider.ResourcePermissions
linkType libregraph.SharingLinkType
}

// GetPermissions returns the cs3 permissions type
func (l *LinkType) GetPermissions() *provider.ResourcePermissions {
micbar marked this conversation as resolved.
Show resolved Hide resolved
if l != nil {
return l.Permissions
}
return nil
}

// SharingLinkTypeFromCS3Permissions creates a libregraph link type
// It returns a list of libregraph actions when the conversion is not possible
func SharingLinkTypeFromCS3Permissions(permissions *linkv1beta1.PublicSharePermissions) (*libregraph.SharingLinkType, []string) {
linkTypes := GetAvailableLinkTypes()
for _, linkType := range linkTypes {
if grants.PermissionsEqual(linkType.GetPermissions(), permissions.GetPermissions()) {
return &linkType.linkType, nil
}
}
return nil, unifiedrole.CS3ResourcePermissionsToLibregraphActions(*permissions.GetPermissions())
}

// CS3ResourcePermissionsFromSharingLink creates a cs3 resource permissions type
// it returns an error when the link type is not allowed or empty
func CS3ResourcePermissionsFromSharingLink(createLink libregraph.DriveItemCreateLink, info provider.ResourceType) (*provider.ResourcePermissions, error) {
switch createLink.GetType() {
case "":
return nil, errors.New("link type is empty")
case libregraph.VIEW:
return NewViewLinkPermissionSet().GetPermissions(), nil
case libregraph.EDIT:
if info == provider.ResourceType_RESOURCE_TYPE_FILE {
return NewFileEditLinkPermissionSet().GetPermissions(), nil
}
return NewFolderEditLinkPermissionSet().GetPermissions(), nil
case libregraph.CREATE_ONLY:
if info == provider.ResourceType_RESOURCE_TYPE_FILE {
return nil, errors.New(NoPermissionMatchError)
}
return NewFolderDropLinkPermissionSet().GetPermissions(), nil
case libregraph.UPLOAD:
if info == provider.ResourceType_RESOURCE_TYPE_FILE {
return nil, errors.New(NoPermissionMatchError)
}
return NewFolderUploadLinkPermissionSet().GetPermissions(), nil
case libregraph.INTERNAL:
return NewInternalLinkPermissionSet().GetPermissions(), nil
default:
return nil, errors.New(NoPermissionMatchError)
}
}

// NewInternalLinkPermissionSet creates cs3 permissions for the internal link type
func NewInternalLinkPermissionSet() *LinkType {
return &LinkType{
Permissions: &provider.ResourcePermissions{},
linkType: libregraph.INTERNAL,
}
}

// NewViewLinkPermissionSet creates cs3 permissions for the view link type
func NewViewLinkPermissionSet() *LinkType {
return &LinkType{
Permissions: &provider.ResourcePermissions{
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
ListContainer: true,
// why is this needed?
ListRecycle: true,
fschade marked this conversation as resolved.
Show resolved Hide resolved
Stat: true,
},
linkType: libregraph.VIEW,
}
}

// NewFileEditLinkPermissionSet creates cs3 permissions for the file edit link type
func NewFileEditLinkPermissionSet() *LinkType {
return &LinkType{
Permissions: &provider.ResourcePermissions{
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
InitiateFileUpload: true,
ListContainer: true,
// why is this needed?
ListRecycle: true,
// why is this needed?
RestoreRecycleItem: true,
Stat: true,
},
linkType: libregraph.EDIT,
}
}

// NewFolderEditLinkPermissionSet creates cs3 permissions for the folder edit link type
func NewFolderEditLinkPermissionSet() *LinkType {
return &LinkType{
Permissions: &provider.ResourcePermissions{
CreateContainer: true,
Delete: true,
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
InitiateFileUpload: true,
ListContainer: true,
// why is this needed?
ListRecycle: true,
Move: true,
// why is this needed?
RestoreRecycleItem: true,
Stat: true,
},
linkType: libregraph.EDIT,
}
}

// NewFolderDropLinkPermissionSet creates cs3 permissions for the folder createOnly link type
func NewFolderDropLinkPermissionSet() *LinkType {
return &LinkType{
Permissions: &provider.ResourcePermissions{
Stat: true,
GetPath: true,
CreateContainer: true,
InitiateFileUpload: true,
},
linkType: libregraph.CREATE_ONLY,
}
}

// NewFolderUploadLinkPermissionSet creates cs3 permissions for the folder upload link type
func NewFolderUploadLinkPermissionSet() *LinkType {
return &LinkType{
Permissions: &provider.ResourcePermissions{
CreateContainer: true,
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
InitiateFileUpload: true,
ListContainer: true,
ListRecycle: true,
Stat: true,
},
linkType: libregraph.UPLOAD,
}
}

// GetAvailableLinkTypes returns a slice of all available link types
func GetAvailableLinkTypes() []*LinkType {
return []*LinkType{
NewInternalLinkPermissionSet(),
NewViewLinkPermissionSet(),
NewFolderUploadLinkPermissionSet(),
NewFileEditLinkPermissionSet(),
NewFolderEditLinkPermissionSet(),
NewFolderDropLinkPermissionSet(),
}
}
13 changes: 13 additions & 0 deletions services/graph/pkg/linktype/linktype_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package linktype_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestLinktype(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Linktype Suite")
}
142 changes: 142 additions & 0 deletions services/graph/pkg/linktype/linktype_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package linktype_test

import (
linkv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/pkg/storage/utils/grants"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/services/graph/pkg/linktype"
)

var _ = Describe("LinktypeFromPermission", func() {
var (
internalLinkType, _ = libregraph.NewSharingLinkTypeFromValue("internal")
createOnlyLinkType, _ = libregraph.NewSharingLinkTypeFromValue("createOnly")
viewLinkType, _ = libregraph.NewSharingLinkTypeFromValue("view")
uploadLinkType, _ = libregraph.NewSharingLinkTypeFromValue("upload")
editLinkType, _ = libregraph.NewSharingLinkTypeFromValue("edit")
folderEditPermsHaveChanged = linktype.NewFolderEditLinkPermissionSet().GetPermissions()
)

BeforeEach(func() {
// simulate that permissions have changed after link creation
folderEditPermsHaveChanged.CreateContainer = false
})

DescribeTable("SharingLinkTypeFromCS3Permissions",
func(permissions *linkv1beta1.PublicSharePermissions,
expectedSharingLinkType *libregraph.SharingLinkType,
expectedActions []string) {

sharingLinkType, actions := linktype.SharingLinkTypeFromCS3Permissions(permissions)
Expect(sharingLinkType).To(Equal(expectedSharingLinkType))
Expect(expectedActions).To(Equal(actions))
},

Entry("Internal",
&linkv1beta1.PublicSharePermissions{Permissions: linktype.NewInternalLinkPermissionSet().GetPermissions()},
internalLinkType,
nil,
),
Entry("CreateOnly",
&linkv1beta1.PublicSharePermissions{Permissions: linktype.NewFolderDropLinkPermissionSet().GetPermissions()},
createOnlyLinkType,
nil,
),
Entry("View File",
&linkv1beta1.PublicSharePermissions{Permissions: linktype.NewViewLinkPermissionSet().GetPermissions()},
viewLinkType,
nil,
),
Entry("Upload in Folder",
&linkv1beta1.PublicSharePermissions{Permissions: linktype.NewFolderUploadLinkPermissionSet().GetPermissions()},
uploadLinkType,
nil,
),
Entry("File Edit",
&linkv1beta1.PublicSharePermissions{Permissions: linktype.NewFileEditLinkPermissionSet().GetPermissions()},
editLinkType,
nil,
),
Entry("Folder Edit",
&linkv1beta1.PublicSharePermissions{Permissions: linktype.NewFolderEditLinkPermissionSet().GetPermissions()},
editLinkType,
nil,
),
Entry("Folder Edit- Permissions have changed after creation",
&linkv1beta1.PublicSharePermissions{Permissions: folderEditPermsHaveChanged},
nil,
[]string{
"libre.graph/driveItem/standard/delete",
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/upload/create",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/path/update",
"libre.graph/driveItem/deleted/update",
"libre.graph/driveItem/basic/read",
},
),
)

DescribeTable("CS3ResourcePermissionsFromSharingLink",
func(createLink libregraph.DriveItemCreateLink,
info provider.ResourceType,
expectedPermissions *provider.ResourcePermissions,
hasError bool) {

permissions, err := linktype.CS3ResourcePermissionsFromSharingLink(createLink, info)
if hasError == true {
Expect(err).To(HaveOccurred())
} else {
Expect(err).ToNot(HaveOccurred())
Expect(grants.PermissionsEqual(permissions, expectedPermissions)).To(BeTrue())
}
},

Entry("Internal",
libregraph.DriveItemCreateLink{Type: internalLinkType},
provider.ResourceType_RESOURCE_TYPE_FILE, linktype.NewInternalLinkPermissionSet().GetPermissions(),
false,
),
Entry("Create Only",
libregraph.DriveItemCreateLink{Type: createOnlyLinkType},
provider.ResourceType_RESOURCE_TYPE_CONTAINER, linktype.NewFolderDropLinkPermissionSet().GetPermissions(),
false,
),
Entry("Create Only",
libregraph.DriveItemCreateLink{Type: createOnlyLinkType},
provider.ResourceType_RESOURCE_TYPE_FILE, linktype.NewFolderDropLinkPermissionSet().GetPermissions(),
true,
),
Entry("View File",
libregraph.DriveItemCreateLink{Type: viewLinkType},
provider.ResourceType_RESOURCE_TYPE_FILE, linktype.NewViewLinkPermissionSet().GetPermissions(),
false,
),
Entry("View Folder",
libregraph.DriveItemCreateLink{Type: viewLinkType},
provider.ResourceType_RESOURCE_TYPE_CONTAINER, linktype.NewViewLinkPermissionSet().GetPermissions(),
false,
),
Entry("Upload in Folder",
libregraph.DriveItemCreateLink{Type: uploadLinkType},
provider.ResourceType_RESOURCE_TYPE_CONTAINER, linktype.NewFolderUploadLinkPermissionSet().GetPermissions(),
false,
),
Entry("File Edit",
libregraph.DriveItemCreateLink{Type: editLinkType},
provider.ResourceType_RESOURCE_TYPE_FILE, linktype.NewFileEditLinkPermissionSet().GetPermissions(),
false,
),
Entry("Folder Edit",
libregraph.DriveItemCreateLink{Type: editLinkType},
provider.ResourceType_RESOURCE_TYPE_CONTAINER, linktype.NewFolderEditLinkPermissionSet().GetPermissions(),
false,
),
)
})
2 changes: 1 addition & 1 deletion services/graph/pkg/service/v0/driveitems.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ func (g Graph) Invite(w http.ResponseWriter, r *http.Request) {
}

if expiration := createShareResponse.GetShare().GetExpiration(); expiration != nil {
permission.ExpirationDateTime = libregraph.PtrTime(utils.TSToTime(expiration))
permission.SetExpirationDateTime(utils.TSToTime(expiration))
}

createShareSuccesses.Store(objectId, permission)
Expand Down
2 changes: 1 addition & 1 deletion services/graph/pkg/service/v0/drives.go
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,7 @@ func (g Graph) cs3PermissionsToLibreGraph(ctx context.Context, space *storagepro
}

if exp := permissionsExpirations[id]; exp != nil {
p.ExpirationDateTime = libregraph.PtrTime(time.Unix(int64(exp.GetSeconds()), int64(exp.GetNanos())))
p.SetExpirationDateTime(time.Unix(int64(exp.GetSeconds()), int64(exp.GetNanos())))
}

// we need to map the permissions to the roles
Expand Down
5 changes: 5 additions & 0 deletions services/graph/pkg/service/v0/errorcode/errorcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ const (
Unauthenticated
// PreconditionFailed the request cannot be made and this error response is sent back
PreconditionFailed
// ItemIsLocked The item is locked by another process. Try again later.
ItemIsLocked
)

var errorCodes = [...]string{
Expand All @@ -80,6 +82,7 @@ var errorCodes = [...]string{
"quotaLimitReached",
"unauthenticated",
"preconditionFailed",
"itemIsLocked",
}

// New constructs a new errorcode.Error
Expand Down Expand Up @@ -129,6 +132,8 @@ func (e Error) Render(w http.ResponseWriter, r *http.Request) {
status = http.StatusConflict
case NotAllowed:
status = http.StatusMethodNotAllowed
case ItemIsLocked:
status = http.StatusLocked
default:
status = http.StatusInternalServerError
}
Expand Down
Loading