Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+480
View File
@@ -0,0 +1,480 @@
// 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 (
"context"
"encoding/base64"
"fmt"
"net/http"
"path"
"path/filepath"
"time"
"github.com/opencloud-eu/reva/v2/pkg/errtypes"
"github.com/opencloud-eu/reva/v2/pkg/mime"
"github.com/opencloud-eu/reva/v2/pkg/publicshare"
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
"github.com/opencloud-eu/reva/v2/pkg/user"
"github.com/opencloud-eu/reva/v2/pkg/utils"
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
publicsharemgr "github.com/opencloud-eu/reva/v2/pkg/publicshare/manager/registry"
usermgr "github.com/opencloud-eu/reva/v2/pkg/user/manager/registry"
)
const (
// ShareTypeUser refers to user shares
ShareTypeUser ShareType = 0
// ShareTypePublicLink refers to public link shares
ShareTypePublicLink ShareType = 3
// ShareTypeGroup represents a group share
ShareTypeGroup ShareType = 1
// ShareTypeFederatedCloudShare represents a federated share
ShareTypeFederatedCloudShare ShareType = 6
// ShareTypeSpaceMembershipUser represents an action regarding user type space members
ShareTypeSpaceMembershipUser ShareType = 7
// ShareTypeSpaceMembershipGroup represents an action regarding group type space members
ShareTypeSpaceMembershipGroup ShareType = 8
// ShareWithUserTypeUser represents a normal user
ShareWithUserTypeUser ShareWithUserType = 0
// ShareWithUserTypeGuest represents a guest user
ShareWithUserTypeGuest ShareWithUserType = 1
// The datetime format of ISO8601
_iso8601 = "2006-01-02T15:04:05Z0700"
)
// ResourceType indicates the OCS type of the resource
type ResourceType int
func (rt ResourceType) String() (s string) {
switch rt {
case 0:
s = "invalid"
case 1:
s = "file"
case 2:
s = "folder"
case 3:
s = "reference"
default:
s = "invalid"
}
return
}
// ShareType denotes a type of share
type ShareType int
// ShareWithUserType denotes a type of user
type ShareWithUserType int
// ShareData represents https://doc.owncloud.com/server/developer_manual/core/ocs-share-api.html#response-attributes-1
type ShareData struct {
// TODO int?
ID string `json:"id" xml:"id"`
// The shares type
ShareType ShareType `json:"share_type" xml:"share_type"`
// The username of the owner of the share.
UIDOwner string `json:"uid_owner" xml:"uid_owner"`
// The display name of the owner of the share.
DisplaynameOwner string `json:"displayname_owner" xml:"displayname_owner"`
// Additional info to identify the share owner, eg. the email or username
AdditionalInfoOwner string `json:"additional_info_owner" xml:"additional_info_owner"`
// The permission attribute set on the file.
// TODO(jfd) change the default to read only
Permissions Permissions `json:"permissions" xml:"permissions"`
// The UNIX timestamp when the share was created.
STime uint64 `json:"stime" xml:"stime"`
// ?
Parent string `json:"parent" xml:"parent"`
// The UNIX timestamp when the share expires.
Expiration string `json:"expiration" xml:"expiration"`
// The public link to the item being shared.
Token string `json:"token" xml:"token"`
// The unique id of the user that owns the file or folder being shared.
UIDFileOwner string `json:"uid_file_owner" xml:"uid_file_owner"`
// The display name of the user that owns the file or folder being shared.
DisplaynameFileOwner string `json:"displayname_file_owner" xml:"displayname_file_owner"`
// Additional info to identify the file owner, eg. the email or username
AdditionalInfoFileOwner string `json:"additional_info_file_owner" xml:"additional_info_file_owner"`
// share state, 0 = accepted, 1 = pending, 2 = declined
State int `json:"state" xml:"state"`
// The path to the shared file or folder.
Path string `json:"path" xml:"path"`
// The type of the object being shared. This can be one of 'file' or 'folder'.
ItemType string `json:"item_type" xml:"item_type"`
// The RFC2045-compliant mimetype of the file.
MimeType string `json:"mimetype" xml:"mimetype"`
// The space ID of the original file location
SpaceID string `json:"space_id" xml:"space_id"`
// The space alias of the original file location
SpaceAlias string `json:"space_alias" xml:"space_alias"`
StorageID string `json:"storage_id" xml:"storage_id"`
Storage uint64 `json:"storage" xml:"storage"`
// The unique node id of the item being shared.
ItemSource string `json:"item_source" xml:"item_source"`
// The unique node id of the item being shared. For legacy reasons item_source and file_source attributes have the same value.
FileSource string `json:"file_source" xml:"file_source"`
// The unique node id of the parent node of the item being shared.
FileParent string `json:"file_parent" xml:"file_parent"`
// The basename of the shared file.
FileTarget string `json:"file_target" xml:"file_target"`
// The uid of the share recipient. This is either
// - a GID (group id) if it is being shared with a group or
// - a UID (user id) if the share is shared with a user.
// - a password for public links
ShareWith string `json:"share_with,omitempty" xml:"share_with,omitempty"`
// The type of user
// - 0 = normal user
// - 1 = guest account
ShareWithUserType ShareWithUserType `json:"share_with_user_type" xml:"share_with_user_type"`
// The display name of the share recipient
ShareWithDisplayname string `json:"share_with_displayname,omitempty" xml:"share_with_displayname,omitempty"`
// Additional info to identify the share recipient, eg. the email or username
ShareWithAdditionalInfo string `json:"share_with_additional_info" xml:"share_with_additional_info"`
// Whether the recipient was notified, by mail, about the share being shared with them.
MailSend int `json:"mail_send" xml:"mail_send"`
// Name of the public share
Name string `json:"name" xml:"name"`
// URL of the public share
URL string `json:"url,omitempty" xml:"url,omitempty"`
// Attributes associated
Attributes string `json:"attributes,omitempty" xml:"attributes,omitempty"`
// Quicklink indicates if the link is the quicklink
Quicklink bool `json:"quicklink,omitempty" xml:"quicklink,omitempty"`
// PasswordProtected represents a public share is password protected
// PasswordProtected bool `json:"password_protected,omitempty" xml:"password_protected,omitempty"`
Hidden bool `json:"hidden" xml:"hidden"`
}
// ShareeData holds share recipient search results
type ShareeData struct {
Exact *ExactMatchesData `json:"exact" xml:"exact"`
Users []*MatchData `json:"users" xml:"users>element"`
Groups []*MatchData `json:"groups" xml:"groups>element"`
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"`
Aliaslink bool `json:"alias_link" xml:"alias_link"`
// if not password protected
ID string `json:"id" xml:"id"`
StorageID string `json:"storage_id" xml:"storage_id"`
SpaceID string `json:"space_id" xml:"space_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"`
SpaceType string `json:"space_type" xml:"space_type"`
}
// ExactMatchesData hold exact matches
type ExactMatchesData struct {
Users []*MatchData `json:"users" xml:"users>element"`
Groups []*MatchData `json:"groups" xml:"groups>element"`
Remotes []*MatchData `json:"remotes" xml:"remotes>element"`
}
// MatchData describes a single match
type MatchData struct {
Label string `json:"label" xml:"label,omitempty"`
Value *MatchValueData `json:"value" xml:"value"`
}
// MatchValueData holds the type and actual value
type MatchValueData struct {
ShareType int `json:"shareType" xml:"shareType"`
ShareWith string `json:"shareWith" xml:"shareWith"`
ShareWithProvider string `json:"shareWithProvider" xml:"shareWithProvider"`
ShareWithAdditionalInfo string `json:"shareWithAdditionalInfo" xml:"shareWithAdditionalInfo,omitempty"`
UserType int `json:"userType" xml:"userType"`
}
// CS3Share2ShareData converts a cs3api user share into shareData data model
func CS3Share2ShareData(ctx context.Context, share *collaboration.Share) *ShareData {
sd := &ShareData{
// share.permissions are mapped below
// Displaynames are added later
UIDOwner: LocalUserIDToString(share.GetCreator()),
UIDFileOwner: LocalUserIDToString(share.GetOwner()),
}
switch share.Grantee.Type {
case provider.GranteeType_GRANTEE_TYPE_USER:
sd.ShareType = ShareTypeUser
sd.ShareWith = LocalUserIDToString(share.Grantee.GetUserId())
shareType := share.GetGrantee().GetUserId().GetType()
if shareType == userpb.UserType_USER_TYPE_LIGHTWEIGHT || shareType == userpb.UserType_USER_TYPE_GUEST {
sd.ShareWithUserType = ShareWithUserTypeGuest
} else {
sd.ShareWithUserType = ShareWithUserTypeUser
}
case provider.GranteeType_GRANTEE_TYPE_GROUP:
sd.ShareType = ShareTypeGroup
sd.ShareWith = LocalGroupIDToString(share.Grantee.GetGroupId())
}
if share.Id != nil {
sd.ID = share.Id.OpaqueId
}
if share.GetPermissions().GetPermissions() != nil {
sd.Permissions = RoleFromResourcePermissions(share.GetPermissions().GetPermissions(), false).OCSPermissions()
}
if share.Ctime != nil {
sd.STime = share.Ctime.Seconds // TODO CS3 api birth time = btime
}
if share.Expiration != nil {
expiration := time.Unix(int64(share.Expiration.Seconds), int64(share.Expiration.Nanos))
sd.Expiration = expiration.Format(_iso8601)
}
return sd
}
// PublicShare2ShareData converts a cs3api public share into shareData data model
func PublicShare2ShareData(share *link.PublicShare, r *http.Request, publicURL string) *ShareData {
sd := &ShareData{
// share.permissions are mapped below
// Displaynames are added later
ShareType: ShareTypePublicLink,
Token: share.Token,
Name: share.DisplayName,
MailSend: 0,
URL: publicURL + path.Join("/", "s/"+share.Token),
UIDOwner: LocalUserIDToString(share.Creator),
UIDFileOwner: LocalUserIDToString(share.Owner),
Quicklink: share.Quicklink,
}
if share.Id != nil {
sd.ID = share.Id.OpaqueId
}
if s := share.GetPermissions().GetPermissions(); s != nil {
sd.Permissions = RoleFromResourcePermissions(share.GetPermissions().GetPermissions(), true).OCSPermissions()
}
if share.Expiration != nil {
sd.Expiration = timestampToExpiration(share.Expiration)
}
if share.Ctime != nil {
sd.STime = share.Ctime.Seconds // TODO CS3 api birth time = btime
}
// hide password
if share.PasswordProtected {
sd.ShareWith = "***redacted***"
sd.ShareWithDisplayname = "***redacted***"
}
return sd
}
func formatRemoteUser(u *userpb.UserId) string {
return fmt.Sprintf("%s@%s", u.OpaqueId, u.Idp)
}
func webdavInfo(protocols []*ocm.Protocol) (*ocm.WebDAVProtocol, bool) {
for _, p := range protocols {
if opt, ok := p.Term.(*ocm.Protocol_WebdavOptions); ok {
return opt.WebdavOptions, true
}
}
return nil, false
}
// ReceivedOCMShare2ShareData converts a cs3 ocm received share into a share data model.
func ReceivedOCMShare2ShareData(share *ocm.ReceivedShare, path string) (*ShareData, error) {
webdav, ok := webdavInfo(share.Protocols)
if !ok {
return nil, errtypes.InternalError("webdav endpoint not in share")
}
opaqueid := base64.StdEncoding.EncodeToString([]byte("/"))
shareTarget := filepath.Join("/Shares", share.Name)
s := &ShareData{
ID: share.Id.OpaqueId,
UIDOwner: formatRemoteUser(share.Creator),
UIDFileOwner: formatRemoteUser(share.Owner),
ShareWith: share.Grantee.GetUserId().OpaqueId,
Permissions: RoleFromResourcePermissions(webdav.GetPermissions().GetPermissions(), false).OCSPermissions(),
ShareType: ShareTypeFederatedCloudShare,
Path: shareTarget,
FileTarget: shareTarget,
MimeType: mime.Detect(share.ResourceType == provider.ResourceType_RESOURCE_TYPE_CONTAINER, share.Name), //nolint:staticcheck // we will update our ocm implementation later
ItemType: ResourceType(share.ResourceType).String(), //nolint:staticcheck // we will update our ocm implementation later
ItemSource: storagespace.FormatResourceID(&provider.ResourceId{
StorageId: utils.OCMStorageProviderID,
SpaceId: share.Id.OpaqueId,
OpaqueId: opaqueid,
}),
STime: share.Ctime.Seconds,
Name: share.Name,
SpaceID: storagespace.FormatStorageID(utils.OCMStorageProviderID, share.Id.OpaqueId),
}
if share.Expiration != nil {
s.Expiration = timestampToExpiration(share.Expiration)
}
return s, nil
}
func webdavAMInfo(methods []*ocm.AccessMethod) (*ocm.WebDAVAccessMethod, bool) {
for _, a := range methods {
if opt, ok := a.Term.(*ocm.AccessMethod_WebdavOptions); ok {
return opt.WebdavOptions, true
}
}
return nil, false
}
// OCMShare2ShareData converts a cs3 ocm share into a share data model.
func OCMShare2ShareData(share *ocm.Share) (*ShareData, error) {
webdav, ok := webdavAMInfo(share.AccessMethods)
if !ok {
return nil, errtypes.InternalError("webdav endpoint not in share")
}
s := &ShareData{
ID: share.Id.OpaqueId,
UIDOwner: share.Creator.OpaqueId,
UIDFileOwner: share.Owner.OpaqueId,
ShareWith: formatRemoteUser(share.Grantee.GetUserId()),
Permissions: RoleFromResourcePermissions(webdav.GetPermissions(), false).OCSPermissions(),
ShareType: ShareTypeFederatedCloudShare,
STime: share.Ctime.Seconds,
Name: share.Name,
}
if share.Expiration != nil {
s.Expiration = timestampToExpiration(share.Expiration)
}
return s, nil
}
// LocalUserIDToString transforms a cs3api user id into an ocs data model without domain name
// TODO ocs uses user names ... so an additional lookup is needed. see mapUserIds()
func LocalUserIDToString(userID *userpb.UserId) string {
if userID == nil || userID.OpaqueId == "" {
return ""
}
return userID.OpaqueId
}
// LocalGroupIDToString transforms a cs3api group id into an ocs data model without domain name
func LocalGroupIDToString(groupID *grouppb.GroupId) string {
if groupID == nil || groupID.OpaqueId == "" {
return ""
}
return groupID.OpaqueId
}
// GetUserManager returns a connection to a user share manager
func GetUserManager(manager string, m map[string]map[string]interface{}) (user.Manager, error) {
if f, ok := usermgr.NewFuncs[manager]; ok {
return f(m[manager])
}
return nil, fmt.Errorf("driver %s not found for user manager", manager)
}
// GetPublicShareManager returns a connection to a public share manager
func GetPublicShareManager(manager string, m map[string]map[string]interface{}) (publicshare.Manager, error) {
if f, ok := publicsharemgr.NewFuncs[manager]; ok {
return f(m[manager])
}
return nil, fmt.Errorf("driver %s not found for public shares manager", manager)
}
// timestamp is assumed to be UTC ... just human readable ...
// FIXME and ambiguous / error prone because there is no time zone ...
func timestampToExpiration(t *types.Timestamp) string {
return time.Unix(int64(t.Seconds), int64(t.Nanos)).UTC().Format("2006-01-02 15:05:05")
}
// ParseTimestamp tries to parse the ocs expiry into a CS3 Timestamp
func ParseTimestamp(timestampString string) (*types.Timestamp, error) {
parsedTime, err := time.Parse("2006-01-02T15:04:05Z0700", timestampString)
if err != nil {
parsedTime, err = time.Parse("2006-01-02", timestampString)
if err == nil {
// the link needs to be valid for the whole day
parsedTime = parsedTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
}
}
if err != nil {
return nil, fmt.Errorf("datetime format invalid: %v, %s", timestampString, err.Error())
}
final := parsedTime.UnixNano()
return &types.Timestamp{
Seconds: uint64(final / 1000000000),
Nanos: uint32(final % 1000000000),
}, nil
}
// UserTypeString returns human readable strings for various user types
func UserTypeString(userType userpb.UserType) string {
switch userType {
case userpb.UserType_USER_TYPE_PRIMARY:
return "primary"
case userpb.UserType_USER_TYPE_SECONDARY:
return "secondary"
case userpb.UserType_USER_TYPE_SERVICE:
return "service"
case userpb.UserType_USER_TYPE_APPLICATION:
return "application"
case userpb.UserType_USER_TYPE_GUEST:
return "guest"
case userpb.UserType_USER_TYPE_FEDERATED:
return "federated"
case userpb.UserType_USER_TYPE_LIGHTWEIGHT:
return "lightweight"
}
return "invalid"
}
@@ -0,0 +1,78 @@
// Copyright 2020 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
import (
"errors"
"fmt"
"strconv"
)
// Permissions reflects the CRUD permissions used in the OCS sharing API
type Permissions uint
const (
// PermissionInvalid represents an invalid permission
PermissionInvalid Permissions = 0
// PermissionRead grants read permissions on a resource
PermissionRead Permissions = 1 << (iota - 1)
// PermissionWrite grants write permissions on a resource
PermissionWrite
// PermissionCreate grants create permissions on a resource
PermissionCreate
// PermissionDelete grants delete permissions on a resource
PermissionDelete
// PermissionShare grants share permissions on a resource
PermissionShare
// PermissionAll grants all permissions on a resource
PermissionAll Permissions = (1 << (iota - 1)) - 1
// PermissionMaxInput is to be used within value range checks
PermissionMaxInput = PermissionAll
// PermissionMinInput is to be used within value range checks
PermissionMinInput = PermissionRead
// PermissionsNone is to be used to deny access on a resource
PermissionsNone = 64
)
var (
// ErrPermissionNotInRange defines a permission specific error.
ErrPermissionNotInRange = fmt.Errorf("the provided permission is not between %d and %d", PermissionMinInput, PermissionMaxInput)
// ErrZeroPermission defines a permission specific error
ErrZeroPermission = errors.New("permission is zero")
)
// 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, ErrZeroPermission
} else if val < int(PermissionInvalid) || int(PermissionMaxInput) < val {
return PermissionInvalid, ErrPermissionNotInRange
}
return Permissions(val), nil
}
// Contain tests if the permissions contain another one.
func (p Permissions) Contain(other Permissions) bool {
return p&other == other
}
func (p Permissions) String() string {
return strconv.FormatUint(uint64(p), 10)
}
+651
View File
@@ -0,0 +1,651 @@
// 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()
case RoleCoowner:
return NewCoownerRole()
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
}