Files
QSfera/vendor/github.com/opencloud-eu/reva/v2/pkg/conversions/role.go
T

650 lines
19 KiB
Go

// Copyright 2018-2021 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 conversions sits between CS3 type definitions and OCS API Responses
package conversions
import (
"fmt"
"strings"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/grants"
)
// Role is a set of ocs permissions and cs3 resource permissions under a common name.
type Role struct {
Name string
cS3ResourcePermissions *provider.ResourcePermissions
ocsPermissions Permissions
}
const (
// RoleViewer grants non-editor role on a resource.
RoleViewer = "viewer"
// RoleViewerListGrants grants non-editor role on a resource.
RoleViewerListGrants = "viewer-list-grants"
// RoleSpaceViewer grants non-editor role on a space.
RoleSpaceViewer = "spaceviewer"
// RoleEditor grants editor permission on a resource, including folders.
RoleEditor = "editor"
// RoleEditorListGrants grants editor permission on a resource, including folders.
RoleEditorListGrants = "editor-list-grants"
// RoleSpaceEditor grants editor permission on a space.
RoleSpaceEditor = "spaceeditor"
// RoleSpaceEditorWithoutVersions grants editor permission without list/restore versions on a space.
RoleSpaceEditorWithoutVersions = "spaceeditor-without-versions"
// RoleFileEditor grants editor permission on a single file.
RoleFileEditor = "file-editor"
// RoleFileEditorListGrants grants editor permission on a single file.
RoleFileEditorListGrants = "file-editor-list-grants"
// RoleCoowner grants co-owner permissions on a resource.
RoleCoowner = "coowner"
// RoleEditorLite grants permission to upload and download to a resource.
RoleEditorLite = "editor-lite"
// RoleUploader grants uploader permission to upload onto a resource (no download).
RoleUploader = "uploader"
// RoleManager grants manager permissions on a resource. Semantically equivalent to co-owner.
RoleManager = "manager"
// RoleSecureViewer grants secure view permissions on a resource or space.
RoleSecureViewer = "secure-viewer"
// 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
func (r *Role) CS3ResourcePermissions() *provider.ResourcePermissions {
return r.cS3ResourcePermissions
}
// OCSPermissions for the role
func (r *Role) OCSPermissions() Permissions {
return r.ocsPermissions
}
// WebDAVPermissions returns the webdav permissions used in propfinds, eg. "WCKDNVR"
/*
from https://github.com/owncloud/core/blob/10715e2b1c85fc3855a38d2b1fe4426b5e3efbad/apps/dav/lib/Files/PublicFiles/SharedNodeTrait.php#L196-L215
$p = '';
if ($node->isDeletable() && $this->checkSharePermissions(Constants::PERMISSION_DELETE)) {
$p .= 'D';
}
if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) {
$p .= 'NV'; // Renameable, Moveable
}
if ($node->getType() === \OCP\Files\FileInfo::TYPE_FILE) {
if ($node->isUpdateable() && $this->checkSharePermissions(Constants::PERMISSION_UPDATE)) {
$p .= 'W';
}
} else {
if ($node->isCreatable() && $this->checkSharePermissions(Constants::PERMISSION_CREATE)) {
$p .= 'CK';
}
}
*/
// D = delete
// NV = update (renameable moveable)
// W = update (files only)
// CK = create (folders only)
// S = Shared
// R = Shareable
// M = Mounted
// Z = Deniable (NEW)
// P = Purge from trashbin
// X = SecureViewable
func (r *Role) WebDAVPermissions(isDir, isShared, isMountpoint, isPublic bool) string {
var b strings.Builder
if !isPublic && isShared {
fmt.Fprintf(&b, "S")
}
if r.ocsPermissions.Contain(PermissionShare) {
fmt.Fprintf(&b, "R")
}
if !isPublic && isMountpoint {
fmt.Fprintf(&b, "M")
}
if r.ocsPermissions.Contain(PermissionDelete) {
fmt.Fprintf(&b, "D") // TODO oc10 shows received shares as deletable
}
if r.ocsPermissions.Contain(PermissionWrite) {
// Single file public link shares cannot be renamed
if !isPublic || (isPublic && r.cS3ResourcePermissions != nil && r.cS3ResourcePermissions.Move) {
fmt.Fprintf(&b, "NV")
}
if !isDir {
fmt.Fprintf(&b, "W")
}
}
if isDir && r.ocsPermissions.Contain(PermissionCreate) {
fmt.Fprintf(&b, "CK")
}
if r.CS3ResourcePermissions().DenyGrant {
fmt.Fprintf(&b, "Z")
}
if r.CS3ResourcePermissions().PurgeRecycle {
fmt.Fprintf(&b, "P")
}
if r.Name == RoleSecureViewer {
fmt.Fprintf(&b, "X")
}
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 RoleViewerListGrants:
return NewViewerListGrantsRole()
case RoleSpaceViewer:
return NewSpaceViewerRole()
case RoleEditor:
return NewEditorRole()
case RoleEditorListGrants:
return NewEditorListGrantsRole()
case RoleSpaceEditor:
return NewSpaceEditorRole()
case RoleFileEditor:
return NewFileEditorRole()
case RoleFileEditorListGrants:
return NewFileEditorListGrantsRole()
case RoleUploader:
return NewUploaderRole()
case RoleManager:
return NewManagerRole()
case RoleSecureViewer:
return NewSecureViewerRole()
default:
return NewUnknownRole()
}
}
// NewUnknownRole creates an unknown role. An Unknown role has no permissions over a cs3 resource nor any ocs endpoint.
func NewUnknownRole() *Role {
return &Role{
Name: RoleUnknown,
cS3ResourcePermissions: &provider.ResourcePermissions{},
ocsPermissions: PermissionInvalid,
}
}
// NewDeniedRole creates a fully denied role
func NewDeniedRole() *Role {
return &Role{
Name: RoleDenied,
cS3ResourcePermissions: &provider.ResourcePermissions{},
ocsPermissions: PermissionsNone,
}
}
// NewViewerRole creates a viewer role. `sharing` indicates if sharing permission should be added
func NewViewerRole() *Role {
p := PermissionRead
return &Role{
Name: RoleViewer,
cS3ResourcePermissions: &provider.ResourcePermissions{
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
ListContainer: true,
ListRecycle: true,
Stat: true,
},
ocsPermissions: p,
}
}
// NewViewerListGrantsRole creates a viewer role. `sharing` indicates if sharing permission should be added
func NewViewerListGrantsRole() *Role {
role := NewViewerRole()
role.cS3ResourcePermissions.ListGrants = true
return role
}
// NewSpaceViewerRole creates a spaceviewer role
func NewSpaceViewerRole() *Role {
return &Role{
Name: RoleSpaceViewer,
cS3ResourcePermissions: &provider.ResourcePermissions{
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
ListContainer: true,
ListGrants: true,
ListRecycle: true,
Stat: true,
},
ocsPermissions: PermissionRead,
}
}
// NewEditorRole creates an editor role. `sharing` indicates if sharing permission should be added
func NewEditorRole() *Role {
p := PermissionRead | PermissionCreate | PermissionWrite | PermissionDelete
return &Role{
Name: RoleEditor,
cS3ResourcePermissions: &provider.ResourcePermissions{
CreateContainer: true,
Delete: true,
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
InitiateFileUpload: true,
ListContainer: true,
ListRecycle: true,
Move: true,
RestoreRecycleItem: true,
Stat: true,
},
ocsPermissions: p,
}
}
// NewEditorListGrantsRole creates an editor role. `sharing` indicates if sharing permission should be added
func NewEditorListGrantsRole() *Role {
role := NewEditorRole()
role.cS3ResourcePermissions.ListGrants = true
return role
}
// NewSpaceEditorRole creates an editor role
func NewSpaceEditorRole() *Role {
return &Role{
Name: RoleSpaceEditor,
cS3ResourcePermissions: &provider.ResourcePermissions{
CreateContainer: true,
Delete: true,
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
InitiateFileUpload: true,
ListContainer: true,
ListFileVersions: true,
ListGrants: true,
ListRecycle: true,
Move: true,
RestoreFileVersion: true,
RestoreRecycleItem: true,
Stat: true,
},
ocsPermissions: PermissionRead | PermissionCreate | PermissionWrite | PermissionDelete,
}
}
// NewSpaceEditorWithoutVersionsRole creates an editor without list/restore versions role
func NewSpaceEditorWithoutVersionsRole() *Role {
return &Role{
Name: RoleSpaceEditorWithoutVersions,
cS3ResourcePermissions: &provider.ResourcePermissions{
CreateContainer: true,
Delete: true,
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
InitiateFileUpload: true,
ListContainer: true,
ListGrants: true,
ListRecycle: true,
Move: true,
RestoreRecycleItem: true,
Stat: true,
},
ocsPermissions: PermissionRead | PermissionCreate | PermissionWrite | PermissionDelete,
}
}
// NewFileEditorRole creates a file-editor role
func NewFileEditorRole() *Role {
p := PermissionRead | PermissionWrite
return &Role{
Name: RoleEditor,
cS3ResourcePermissions: &provider.ResourcePermissions{
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
ListContainer: true,
ListRecycle: true,
Stat: true,
InitiateFileUpload: true,
RestoreRecycleItem: true,
},
ocsPermissions: p,
}
}
// NewFileEditorListGrantsRole creates a file-editor role
func NewFileEditorListGrantsRole() *Role {
role := NewFileEditorRole()
role.cS3ResourcePermissions.ListGrants = true
return role
}
// NewCoownerRole creates a coowner role.
func NewCoownerRole() *Role {
return &Role{
Name: RoleCoowner,
cS3ResourcePermissions: &provider.ResourcePermissions{
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
ListGrants: true,
ListContainer: true,
ListFileVersions: true,
ListRecycle: true,
Stat: true,
InitiateFileUpload: true,
RestoreFileVersion: true,
RestoreRecycleItem: true,
CreateContainer: true,
Delete: true,
Move: true,
PurgeRecycle: true,
AddGrant: true,
UpdateGrant: true,
RemoveGrant: true,
},
ocsPermissions: PermissionAll,
}
}
// NewEditorLiteRole creates an editor-lite role
func NewEditorLiteRole() *Role {
return &Role{
Name: RoleEditorLite,
cS3ResourcePermissions: &provider.ResourcePermissions{
Stat: true,
GetPath: true,
CreateContainer: true,
InitiateFileUpload: true,
InitiateFileDownload: true,
ListContainer: true,
Move: true,
},
ocsPermissions: PermissionCreate,
}
}
// NewUploaderRole creates an uploader role with no download permissions
func NewUploaderRole() *Role {
return &Role{
Name: RoleUploader,
cS3ResourcePermissions: &provider.ResourcePermissions{
Stat: true,
GetPath: true,
CreateContainer: true,
InitiateFileUpload: true,
},
ocsPermissions: PermissionCreate,
}
}
// 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{
Name: RoleManager,
cS3ResourcePermissions: &provider.ResourcePermissions{
GetPath: true,
GetQuota: true,
InitiateFileDownload: true,
ListGrants: true,
ListContainer: true,
ListFileVersions: true,
ListRecycle: true,
Stat: true,
InitiateFileUpload: true,
RestoreFileVersion: true,
RestoreRecycleItem: true,
Move: true,
CreateContainer: true,
Delete: true,
PurgeRecycle: true,
// these permissions only make sense to enforce them in the root of the storage space.
AddGrant: true, // managers can add users to the space
RemoveGrant: true, // managers can remove users from the space
UpdateGrant: true,
DenyGrant: true, // managers can deny access to sub folders
},
ocsPermissions: PermissionAll,
}
}
// NewSecureViewerRole creates a secure viewer role
func NewSecureViewerRole() *Role {
return &Role{
Name: RoleSecureViewer,
cS3ResourcePermissions: &provider.ResourcePermissions{
GetPath: true,
ListContainer: true,
Stat: true,
},
}
}
// RoleFromOCSPermissions tries to map ocs permissions to a role
// TODO: rethink using this. ocs permissions cannot be assigned 1:1 to roles
func RoleFromOCSPermissions(p Permissions, ri *provider.ResourceInfo) *Role {
switch {
// Invalid
case p == PermissionInvalid:
return NewNoneRole()
// Uploader
case p == PermissionCreate:
return NewUploaderRole()
// Viewer/SpaceViewer
case p == PermissionRead:
if isSpaceRoot(ri) {
return NewSpaceViewerRole()
}
return NewViewerRole()
// Editor/SpaceEditor
case p.Contain(PermissionRead) && p.Contain(PermissionWrite) && p.Contain(PermissionCreate) && p.Contain(PermissionDelete) && !p.Contain(PermissionShare):
if isSpaceRoot(ri) {
return NewSpaceEditorRole()
}
return NewEditorRole()
// Custom
default:
return NewLegacyRoleFromOCSPermissions(p)
}
}
func isSpaceRoot(ri *provider.ResourceInfo) bool {
if ri == nil {
return false
}
if ri.Type != provider.ResourceType_RESOURCE_TYPE_CONTAINER {
return false
}
if ri.GetId().GetOpaqueId() != ri.GetSpace().GetRoot().GetOpaqueId() ||
ri.GetId().GetSpaceId() != ri.GetSpace().GetRoot().GetSpaceId() ||
ri.GetId().GetStorageId() != ri.GetSpace().GetRoot().GetStorageId() {
return false
}
return true
}
// NewLegacyRoleFromOCSPermissions tries to map a legacy combination of ocs permissions to cs3 resource permissions as a legacy role
func NewLegacyRoleFromOCSPermissions(p Permissions) *Role {
r := &Role{
Name: RoleLegacy, // TODO custom role?
ocsPermissions: p,
cS3ResourcePermissions: &provider.ResourcePermissions{},
}
if p.Contain(PermissionRead) {
r.cS3ResourcePermissions.ListContainer = true
// r.cS3ResourcePermissions.ListGrants = true
r.cS3ResourcePermissions.ListRecycle = true
r.cS3ResourcePermissions.Stat = true
r.cS3ResourcePermissions.GetPath = true
r.cS3ResourcePermissions.GetQuota = true
r.cS3ResourcePermissions.InitiateFileDownload = true
}
if p.Contain(PermissionWrite) {
r.cS3ResourcePermissions.InitiateFileUpload = true
r.cS3ResourcePermissions.RestoreRecycleItem = true
}
if p.Contain(PermissionCreate) {
r.cS3ResourcePermissions.Stat = true
r.cS3ResourcePermissions.CreateContainer = true
// FIXME permissions mismatch: double check ocs create vs update file
// - if the file exists the ocs api needs to check update permission,
// - if the file does not exist the ocs api needs to check update permission
r.cS3ResourcePermissions.InitiateFileUpload = true
if p.Contain(PermissionWrite) {
r.cS3ResourcePermissions.Move = true // TODO move only when create and write?
}
}
if p.Contain(PermissionDelete) {
r.cS3ResourcePermissions.Delete = true
}
if p.Contain(PermissionShare) {
r.cS3ResourcePermissions.AddGrant = true
// r.cS3ResourcePermissions.RemoveGrant = true // TODO when are you able to unshare / delete
// r.cS3ResourcePermissions.UpdateGrant = true
}
return r
}
// RoleFromResourcePermissions tries to map cs3 resource permissions to a role
// It needs to know whether this is a link or not, because empty permissions on links mean "INTERNAL LINK"
// while empty permissions on other resources mean "DENIAL". Obviously this is not optimal.
func RoleFromResourcePermissions(rp *provider.ResourcePermissions, islink bool) *Role {
r := &Role{
Name: RoleUnknown,
ocsPermissions: PermissionInvalid,
cS3ResourcePermissions: rp,
}
if rp == nil {
return r
}
if grants.PermissionsEqual(rp, &provider.ResourcePermissions{}) {
if !islink {
r.ocsPermissions = PermissionsNone
r.Name = RoleDenied
}
return r
}
if rp.ListContainer &&
rp.ListRecycle &&
rp.Stat &&
rp.GetPath &&
rp.GetQuota &&
rp.InitiateFileDownload {
r.ocsPermissions |= PermissionRead
}
if rp.InitiateFileUpload &&
rp.RestoreRecycleItem {
r.ocsPermissions |= PermissionWrite
}
if rp.Stat &&
rp.CreateContainer &&
rp.InitiateFileUpload {
r.ocsPermissions |= PermissionCreate
}
if rp.Delete {
r.ocsPermissions |= PermissionDelete
}
if rp.AddGrant {
r.ocsPermissions |= PermissionShare
}
if r.ocsPermissions.Contain(PermissionRead) {
if r.ocsPermissions.Contain(PermissionWrite) && r.ocsPermissions.Contain(PermissionCreate) && r.ocsPermissions.Contain(PermissionDelete) && r.ocsPermissions.Contain(PermissionShare) {
r.Name = RoleEditor
if rp.ListGrants {
r.Name = RoleEditorListGrants
}
if rp.RemoveGrant {
r.Name = RoleManager
}
return r // editor or manager
}
if r.ocsPermissions == PermissionRead|PermissionShare {
r.Name = RoleViewer
if rp.ListGrants {
r.Name = RoleViewerListGrants
}
return r
}
} else if rp.Stat && rp.GetPath && rp.ListContainer && !rp.InitiateFileUpload && !rp.Delete && !rp.AddGrant {
r.Name = RoleSecureViewer
return r
}
if r.ocsPermissions == PermissionCreate {
if rp.GetPath && rp.InitiateFileDownload && rp.ListContainer && rp.Move {
r.Name = RoleEditorLite
return r
}
r.Name = RoleUploader
return r
}
r.Name = RoleLegacy
// at this point other ocs permissions may have been mapped.
// TODO what about even more granular cs3 permissions?, eg. only stat
return r
}
// SufficientCS3Permissions returns true if the `existing` permissions contain the `requested` permissions
func SufficientCS3Permissions(existing, requested *provider.ResourcePermissions) bool {
if existing == nil || requested == nil {
return false
}
// empty permissions represent a denial
if grants.PermissionsEqual(requested, &provider.ResourcePermissions{}) {
return existing.DenyGrant
}
numFields := existing.ProtoReflect().Descriptor().Fields().Len()
for i := 0; i < numFields; i++ {
field := existing.ProtoReflect().Descriptor().Fields().Get(i)
existingPermission := existing.ProtoReflect().Get(field).Bool()
requestedPermission := requested.ProtoReflect().Get(field).Bool()
// every requested permission needs to exist for the creator
if requestedPermission && !existingPermission {
return false
}
}
return true
}