Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/unreleased/deny-role.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: add support for denying access in OCS layer

http://github.com/cs3org/reva/pull/1949
2 changes: 1 addition & 1 deletion cmd/reva/share-create.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func getSharePerm(p string) (*provider.ResourcePermissions, error) {
case editorPermission:
return conversions.NewEditorRole().CS3ResourcePermissions(), nil
case collabPermission:
return conversions.NewCoownerRole().CS3ResourcePermissions(), nil
return conversions.NewCollaboratorRole().CS3ResourcePermissions(), nil
case denyPermission:
return &provider.ResourcePermissions{}, nil
default:
Expand Down
19 changes: 14 additions & 5 deletions internal/http/services/owncloud/ocs/conversions/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
type Permissions uint

const (
// PermissionInvalid grants no permissions on a resource
// PermissionInvalid represents an invalid permission
PermissionInvalid Permissions = 0
// PermissionRead grants read permissions on a resource
PermissionRead Permissions = 1 << (iota - 1)
Expand All @@ -38,21 +38,30 @@ const (
PermissionDelete
// PermissionShare grants share permissions on a resource
PermissionShare
// PermissionDeny grants permissions to deny access on a resource
// The recipient of the resource will then have PermissionNone.
PermissionDeny
// PermissionNone grants no permissions on a resource
PermissionNone
// PermissionMax is to be used within value range checks
PermissionMax Permissions = (1 << (iota - 1)) - 1
// PermissionAll grants all permissions on a resource
PermissionAll Permissions = (1 << (iota - 1)) - 1
PermissionAll = PermissionMax - PermissionNone
// PermissionMin is to be used within value range checks
PermissionMin = PermissionRead
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not change the bitmask. We should use roles instead. The permissions bitmask is considered deprecated and will not work in spaces.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fine, this PR is not expected to work with spaces, only for the master branch.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok but the testsuite will never pass on this. The permissions bitmask ends with 31 everything above that will be considered as "invalid permission"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we found that earlier, I think we will add those tests in the expected failures files for time being as this will (are) fixed in the edge branch.

)

var (
// ErrPermissionNotInRange defines a permission specific error.
ErrPermissionNotInRange = fmt.Errorf("The provided permission is not between %d and %d", PermissionInvalid, PermissionAll)
ErrPermissionNotInRange = fmt.Errorf("The provided permission is not between %d and %d", PermissionMin, PermissionMax)
)

// NewPermissions creates a new Permissions instance.
// 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)
} else if val < int(PermissionInvalid) || int(PermissionAll) < val {
return PermissionInvalid, fmt.Errorf("permissions %d out of range %d - %d", val, PermissionMin, PermissionMax)
} else if val < int(PermissionInvalid) || int(PermissionMax) < val {
return PermissionInvalid, ErrPermissionNotInRange
}
return Permissions(val), nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
)

func TestNewPermissions(t *testing.T) {
for val := int(PermissionRead); val <= int(PermissionAll); val++ {
for val := int(PermissionMin); val <= int(PermissionMax); val++ {
_, err := NewPermissions(val)
if err != nil {
t.Errorf("value %d should be a valid permissions", val)
Expand All @@ -35,7 +35,7 @@ func TestNewPermissionsWithInvalidValueShouldFail(t *testing.T) {
vals := []int{
int(PermissionInvalid),
-1,
int(PermissionAll) + 1,
int(PermissionMax) + 1,
}
for _, v := range vals {
_, err := NewPermissions(v)
Expand All @@ -52,10 +52,10 @@ func TestContainPermissionAll(t *testing.T) {
4: PermissionCreate,
8: PermissionDelete,
16: PermissionShare,
31: PermissionAll,
63: PermissionAll,
}

p, _ := NewPermissions(31) // all permissions should contain all other permissions
p, _ := NewPermissions(63) // all permissions should contain all other permissions
for _, value := range table {
if !p.Contain(value) {
t.Errorf("permissions %d should contain %d", p, value)
Expand All @@ -68,7 +68,7 @@ func TestContainPermissionRead(t *testing.T) {
4: PermissionCreate,
8: PermissionDelete,
16: PermissionShare,
31: PermissionAll,
63: PermissionAll,
}

p, _ := NewPermissions(1) // read permission should not contain any other permissions
Expand Down Expand Up @@ -145,10 +145,11 @@ func TestPermissions2Role(t *testing.T) {
table := map[Permissions]string{
PermissionRead: RoleViewer,
PermissionRead | PermissionWrite | PermissionCreate | PermissionDelete: RoleEditor,
PermissionAll: RoleCoowner,
PermissionAll: RoleCollaborator,
PermissionWrite: RoleLegacy,
PermissionShare: RoleLegacy,
PermissionWrite | PermissionShare: RoleLegacy,
PermissionNone: RoleDenied,
}

for permissions, role := range table {
Expand Down
79 changes: 68 additions & 11 deletions internal/http/services/owncloud/ocs/conversions/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"strings"

provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/storage/utils/grants"
)

// Role is a set of ocs permissions and cs3 resource permissions under a common name.
Expand All @@ -36,21 +37,24 @@ type Role struct {
const (
// RoleViewer grants non-editor role on a resource.
RoleViewer = "viewer"
// RoleReader grants non-editor role on a resource
RoleReader = "reader"
// RoleEditor grants editor permission on a resource, including folders.
RoleEditor = "editor"
// RoleFileEditor grants editor permission on a single file.
RoleFileEditor = "file-editor"
// RoleCoowner grants co-owner permissions on a resource.
RoleCoowner = "coowner"
// RoleCollaborator grants editor+resharing permissions on a resource.
RoleCollaborator = "coowner"
// RoleUploader grants uploader permission to upload onto a resource.
RoleUploader = "uploader"
// RoleManager grants manager permissions on a resource. Semantically equivalent to co-owner.
RoleManager = "manager"

// RoleUnknown is used for unknown roles.
RoleUnknown = "unknown"
// RoleLegacy provides backwards compatibility.
RoleLegacy = "legacy"
// RoleDenied grants no permission at all on a resource
RoleDenied = "denied"
)

// CS3ResourcePermissions for the role
Expand Down Expand Up @@ -92,6 +96,7 @@ func (r *Role) OCSPermissions() Permissions {
// S = Shared
// R = Shareable
// M = Mounted
// Z = Deniable (NEW)
func (r *Role) WebDAVPermissions(isDir, isShared, isMountpoint, isPublic bool) string {
var b strings.Builder
if !isPublic && isShared {
Expand All @@ -115,20 +120,29 @@ func (r *Role) WebDAVPermissions(isDir, isShared, isMountpoint, isPublic bool) s
if isDir && r.ocsPermissions.Contain(PermissionCreate) {
fmt.Fprintf(&b, "CK")
}

if r.ocsPermissions.Contain(PermissionDeny) {
fmt.Fprintf(&b, "Z")
}

return b.String()
}

// RoleFromName creates a role from the name
func RoleFromName(name string) *Role {
switch name {
case RoleDenied:
return NewDeniedRole()
case RoleViewer:
return NewViewerRole()
case RoleReader:
return NewReaderRole()
case RoleEditor:
return NewEditorRole()
case RoleFileEditor:
return NewFileEditorRole()
case RoleCoowner:
return NewCoownerRole()
case RoleCollaborator:
return NewCollaboratorRole()
case RoleUploader:
return NewUploaderRole()
case RoleManager:
Expand All @@ -147,6 +161,15 @@ func NewUnknownRole() *Role {
}
}

// NewDeniedRole creates a fully denied role
func NewDeniedRole() *Role {
return &Role{
Name: RoleDenied,
cS3ResourcePermissions: &provider.ResourcePermissions{},
ocsPermissions: PermissionNone,
}
}

// NewViewerRole creates a viewer role
func NewViewerRole() *Role {
return &Role{
Expand All @@ -165,6 +188,25 @@ func NewViewerRole() *Role {
}
}

// NewReaderRole creates a reader role
func NewReaderRole() *Role {
return &Role{
Name: RoleViewer,
cS3ResourcePermissions: &provider.ResourcePermissions{
// read
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
ListGrants: true,
ListContainer: true,
ListFileVersions: true,
ListRecycle: true,
Stat: true,
},
ocsPermissions: PermissionRead,
}
}

// NewEditorRole creates an editor role
func NewEditorRole() *Role {
return &Role{
Expand Down Expand Up @@ -211,10 +253,10 @@ func NewFileEditorRole() *Role {
}
}

// NewCoownerRole creates a coowner role
func NewCoownerRole() *Role {
// NewCollaboratorRole creates a collaborator role
func NewCollaboratorRole() *Role {
return &Role{
Name: RoleCoowner,
Name: RoleCollaborator,
cS3ResourcePermissions: &provider.ResourcePermissions{
GetPath: true,
GetQuota: true,
Expand Down Expand Up @@ -286,10 +328,13 @@ func NewManagerRole() *Role {

// RoleFromOCSPermissions tries to map ocs permissions to a role
func RoleFromOCSPermissions(p Permissions) *Role {
if p.Contain(PermissionNone) {
return NewDeniedRole()
}
if p.Contain(PermissionRead) {
if p.Contain(PermissionWrite) && p.Contain(PermissionCreate) && p.Contain(PermissionDelete) {
if p.Contain(PermissionShare) {
return NewCoownerRole()
return NewCollaboratorRole()
}
return NewEditorRole()
}
Expand Down Expand Up @@ -347,6 +392,9 @@ func NewLegacyRoleFromOCSPermissions(p Permissions) *Role {
r.cS3ResourcePermissions.RemoveGrant = true // TODO when are you able to unshare / delete
r.cS3ResourcePermissions.UpdateGrant = true
}
if p.Contain(PermissionDeny) {
r.cS3ResourcePermissions.DenyGrant = true
}
return r
}

Expand All @@ -360,6 +408,11 @@ func RoleFromResourcePermissions(rp *provider.ResourcePermissions) *Role {
if rp == nil {
return r
}
if grants.PermissionsEqual(rp, &provider.ResourcePermissions{}) {
r.ocsPermissions = PermissionNone
r.Name = RoleDenied
return r
}
if rp.ListContainer &&
rp.ListGrants &&
rp.ListFileVersions &&
Expand Down Expand Up @@ -390,13 +443,17 @@ func RoleFromResourcePermissions(rp *provider.ResourcePermissions) *Role {
rp.UpdateGrant {
r.ocsPermissions |= PermissionShare
}
if rp.DenyGrant {
r.ocsPermissions |= PermissionDeny
}

if r.ocsPermissions.Contain(PermissionRead) {
if r.ocsPermissions.Contain(PermissionWrite) && r.ocsPermissions.Contain(PermissionCreate) && r.ocsPermissions.Contain(PermissionDelete) {
r.Name = RoleEditor
if r.ocsPermissions.Contain(PermissionShare) {
r.Name = RoleCoowner
r.Name = RoleCollaborator
}
return r // editor or coowner
return r // editor or collaborator
}
if r.ocsPermissions == PermissionRead {
r.Name = RoleViewer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,23 +197,23 @@ func (h *Handler) CreateShare(w http.ResponseWriter, r *http.Request) {

switch shareType {
case int(conversions.ShareTypeUser):
// user collaborations default to coowner
if role, val, err := h.extractPermissions(w, r, statRes.Info, conversions.NewCoownerRole()); err == nil {
// user collaborations default to collab
if role, val, err := h.extractPermissions(w, r, statRes.Info, conversions.NewCollaboratorRole()); err == nil {
h.createUserShare(w, r, statRes.Info, role, val)
}
case int(conversions.ShareTypeGroup):
// group collaborations default to coowner
if role, val, err := h.extractPermissions(w, r, statRes.Info, conversions.NewCoownerRole()); err == nil {
// group collaborations default to collab
if role, val, err := h.extractPermissions(w, r, statRes.Info, conversions.NewCollaboratorRole()); err == nil {
h.createGroupShare(w, r, statRes.Info, role, val)
}
case int(conversions.ShareTypePublicLink):
// public links default to read only
if _, _, err := h.extractPermissions(w, r, statRes.Info, conversions.NewViewerRole()); err == nil {
if _, _, err := h.extractPermissions(w, r, statRes.Info, conversions.NewReaderRole()); err == nil {
h.createPublicLinkShare(w, r, statRes.Info)
}
case int(conversions.ShareTypeFederatedCloudShare):
// federated shares default to read only
if role, val, err := h.extractPermissions(w, r, statRes.Info, conversions.NewViewerRole()); err == nil {
if role, val, err := h.extractPermissions(w, r, statRes.Info, conversions.NewReaderRole()); err == nil {
h.createFederatedCloudShare(w, r, statRes.Info, role, val)
}
case int(conversions.ShareTypeSpaceMembership):
Expand Down Expand Up @@ -273,10 +273,17 @@ func (h *Handler) extractPermissions(w http.ResponseWriter, r *http.Request, ri
}
}

existingPermissions := conversions.RoleFromResourcePermissions(ri.PermissionSet).OCSPermissions()
if permissions == conversions.PermissionInvalid || !existingPermissions.Contain(permissions) {
response.WriteOCSError(w, r, http.StatusNotFound, "Cannot set the requested share permissions", nil)
return nil, nil, errors.New("cannot set the requested share permissions")
// add a deny permission only if the user has the grant to deny (ResourcePermissions.DenyGrant == true)
if permissions == conversions.PermissionNone {
if !ri.PermissionSet.DenyGrant {
response.WriteOCSError(w, r, http.StatusNotFound, "Cannot set the requested share permissions: no deny grant on resource", nil)
}
} else {
existingPermissions := conversions.RoleFromResourcePermissions(ri.PermissionSet).OCSPermissions()
if permissions == conversions.PermissionInvalid || !existingPermissions.Contain(permissions) {
response.WriteOCSError(w, r, http.StatusNotFound, "Cannot set the requested share permissions", nil)
return nil, nil, errors.New("cannot set the requested share permissions")
}
}

role = conversions.RoleFromOCSPermissions(permissions)
Expand Down
5 changes: 1 addition & 4 deletions pkg/share/share.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions"
"github.com/cs3org/reva/pkg/utils"
"google.golang.org/genproto/protobuf/field_mask"
)
Expand Down Expand Up @@ -118,9 +117,7 @@ func MatchesFilter(share *collaboration.Share, filter *collaboration.Filter) boo
case collaboration.Filter_TYPE_GRANTEE_TYPE:
return share.Grantee.Type == filter.GetGranteeType()
case collaboration.Filter_TYPE_EXCLUDE_DENIALS:
// This filter type is used to filter out "denial shares". These are currently implemented by having the permission "0".
// I.e. if the permission is 0 we don't want to show it.
return int(conversions.RoleFromResourcePermissions(share.Permissions.Permissions).OCSPermissions()) != 0
return share.Permissions.Permissions.DenyGrant
default:
return false
}
Expand Down