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

Expose capability to deny access in OCS API #2733

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Expose capability to deny access in OCS API
  • Loading branch information
gmgigi96 committed Apr 12, 2022
commit 558631be3233e0227bf98720f495cd1f98bf2b83
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
)

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