diff --git a/changelog/unreleased/cs3-permissions-service.md b/changelog/unreleased/cs3-permissions-service.md new file mode 100644 index 00000000000..b792173e209 --- /dev/null +++ b/changelog/unreleased/cs3-permissions-service.md @@ -0,0 +1,5 @@ +Enhancement: Use CS3 permissions API + +Added calls to the CS3 permissions API to the decomposedfs in order to check the user permissions. + +https://github.com/cs3org/reva/pull/2341 diff --git a/go.mod b/go.mod index a0c8c8cd0fa..7247d2b4b29 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/cheggaaa/pb v1.0.29 github.com/coreos/go-oidc v2.2.1+incompatible github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e - github.com/cs3org/go-cs3apis v0.0.0-20211214102047-7ce3134d7bf8 + github.com/cs3org/go-cs3apis v0.0.0-20211214102128-4e8745ab1654 github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 github.com/eventials/go-tus v0.0.0-20200718001131-45c7ec8f5d59 github.com/gdexlab/go-render v1.0.1 diff --git a/go.sum b/go.sum index 73d811a43ae..42a21787db3 100644 --- a/go.sum +++ b/go.sum @@ -111,8 +111,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJffz4pz0o1WuQxJ28+5x5JgaHD8= github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= -github.com/cs3org/go-cs3apis v0.0.0-20211214102047-7ce3134d7bf8 h1:PqOprF37OvwCbAN5W23znknGk6N/LMayqLAeP904FHE= -github.com/cs3org/go-cs3apis v0.0.0-20211214102047-7ce3134d7bf8/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= +github.com/cs3org/go-cs3apis v0.0.0-20211214102128-4e8745ab1654 h1:ha5tiuuFyDrwKUrVEc3TrRDFgTKVQ9NGDRmEP0PRPno= +github.com/cs3org/go-cs3apis v0.0.0-20211214102128-4e8745ab1654/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 h1:Z9lwXumT5ACSmJ7WGnFl+OMLLjpz5uR2fyz7dC255FI= github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8/go.mod h1:4abs/jPXcmJzYoYGF91JF9Uq9s/KL5n1jvFDix8KcqY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/internal/grpc/services/gateway/gateway.go b/internal/grpc/services/gateway/gateway.go index 3a8a6203a5f..35c14d737b4 100644 --- a/internal/grpc/services/gateway/gateway.go +++ b/internal/grpc/services/gateway/gateway.go @@ -71,6 +71,7 @@ type config struct { EtagCacheTTL int `mapstructure:"etag_cache_ttl"` AllowedUserAgents map[string][]string `mapstructure:"allowed_user_agents"` // map[path][]user-agent CreateHomeCacheTTL int `mapstructure:"create_home_cache_ttl"` + PermissionsEndpoint string `mapstructure:"permissionssvc"` } // sets defaults diff --git a/internal/grpc/services/gateway/permissions.go b/internal/grpc/services/gateway/permissions.go new file mode 100644 index 00000000000..faf26f22450 --- /dev/null +++ b/internal/grpc/services/gateway/permissions.go @@ -0,0 +1,39 @@ +// 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 gateway + +import ( + "context" + + permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" + "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/pkg/errors" +) + +func (s *svc) CheckPermission(ctx context.Context, req *permissions.CheckPermissionRequest) (*permissions.CheckPermissionResponse, error) { + c, err := pool.GetPermissionsClient(s.c.PermissionsEndpoint) + if err != nil { + err = errors.Wrap(err, "gateway: error calling GetPermissionssClient") + return &permissions.CheckPermissionResponse{ + Status: status.NewInternal(ctx, err, "error getting permissions client"), + }, nil + } + return c.CheckPermission(ctx, req) +} diff --git a/internal/grpc/services/storageprovider/storageprovider.go b/internal/grpc/services/storageprovider/storageprovider.go index 13dc51ed157..82e09db0bbc 100644 --- a/internal/grpc/services/storageprovider/storageprovider.go +++ b/internal/grpc/services/storageprovider/storageprovider.go @@ -20,7 +20,6 @@ package storageprovider import ( "context" - "encoding/json" "fmt" "net/url" "os" @@ -579,19 +578,7 @@ func hasNodeID(s *provider.StorageSpace) bool { func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStorageSpacesRequest) (*provider.ListStorageSpacesResponse, error) { log := appctx.GetLogger(ctx) - // This is just a quick hack to get the users permission into reva. - // Replace this as soon as we have a proper system to check the users permissions. - opaque := req.Opaque - var permissions map[string]struct{} - if opaque != nil { - entry := opaque.Map["permissions"] - err := json.Unmarshal(entry.Value, &permissions) - if err != nil { - return nil, err - } - } - - spaces, err := s.storage.ListStorageSpaces(ctx, req.Filters, permissions) + spaces, err := s.storage.ListStorageSpaces(ctx, req.Filters) if err != nil { var st *rpc.Status switch err.(type) { diff --git a/pkg/rgrpc/todo/pool/pool.go b/pkg/rgrpc/todo/pool/pool.go index 550d7d7499a..b2c94b1219b 100644 --- a/pkg/rgrpc/todo/pool/pool.go +++ b/pkg/rgrpc/todo/pool/pool.go @@ -32,6 +32,7 @@ import ( ocmcore "github.com/cs3org/go-cs3apis/cs3/ocm/core/v1beta1" invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" ocmprovider "github.com/cs3org/go-cs3apis/cs3/ocm/provider/v1beta1" + permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" preferences "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" @@ -70,6 +71,7 @@ var ( ocmCores = newProvider() publicShareProviders = newProvider() preferencesProviders = newProvider() + permissionsProviders = newProvider() appRegistries = newProvider() appProviders = newProvider() storageRegistries = newProvider() @@ -349,6 +351,25 @@ func GetPreferencesClient(endpoint string) (preferences.PreferencesAPIClient, er return v, nil } +// GetPermissionsClient returns a new PermissionsClient. +func GetPermissionsClient(endpoint string) (permissions.PermissionsAPIClient, error) { + permissionsProviders.m.Lock() + defer permissionsProviders.m.Unlock() + + if c, ok := permissionsProviders.conn[endpoint]; ok { + return c.(permissions.PermissionsAPIClient), nil + } + + conn, err := NewConn(endpoint) + if err != nil { + return nil, err + } + + v := permissions.NewPermissionsAPIClient(conn) + permissionsProviders.conn[endpoint] = v + return v, nil +} + // GetAppRegistryClient returns a new AppRegistryClient. func GetAppRegistryClient(endpoint string) (appregistry.RegistryAPIClient, error) { appRegistries.m.Lock() diff --git a/pkg/storage/fs/nextcloud/nextcloud.go b/pkg/storage/fs/nextcloud/nextcloud.go index 04e5b549129..34e2fe34527 100644 --- a/pkg/storage/fs/nextcloud/nextcloud.go +++ b/pkg/storage/fs/nextcloud/nextcloud.go @@ -783,7 +783,7 @@ func (nc *StorageDriver) Unlock(ctx context.Context, ref *provider.Reference) er } // ListStorageSpaces as defined in the storage.FS interface -func (nc *StorageDriver) ListStorageSpaces(ctx context.Context, f []*provider.ListStorageSpacesRequest_Filter, _ map[string]struct{}) ([]*provider.StorageSpace, error) { +func (nc *StorageDriver) ListStorageSpaces(ctx context.Context, f []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { bodyStr, _ := json.Marshal(f) _, respBody, err := nc.do(ctx, Action{"ListStorageSpaces", string(bodyStr)}) if err != nil { diff --git a/pkg/storage/fs/nextcloud/nextcloud_test.go b/pkg/storage/fs/nextcloud/nextcloud_test.go index b81edd643b6..f4565a54b59 100644 --- a/pkg/storage/fs/nextcloud/nextcloud_test.go +++ b/pkg/storage/fs/nextcloud/nextcloud_test.go @@ -987,7 +987,7 @@ var _ = Describe("Nextcloud", func() { }, } filters := []*provider.ListStorageSpacesRequest_Filter{filter1, filter2, filter3} - spaces, err := nc.ListStorageSpaces(ctx, filters, nil) + spaces, err := nc.ListStorageSpaces(ctx, filters) Expect(err).ToNot(HaveOccurred()) Expect(len(spaces)).To(Equal(1)) // https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L1341-L1366 diff --git a/pkg/storage/fs/owncloud/owncloud.go b/pkg/storage/fs/owncloud/owncloud.go index 5e0a65bce84..c75f82fde13 100644 --- a/pkg/storage/fs/owncloud/owncloud.go +++ b/pkg/storage/fs/owncloud/owncloud.go @@ -2255,7 +2255,7 @@ func (fs *ocfs) RestoreRecycleItem(ctx context.Context, basePath, key, relativeP return fs.propagate(ctx, tgt) } -func (fs *ocfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, _ map[string]struct{}) ([]*provider.StorageSpace, error) { +func (fs *ocfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { return nil, errtypes.NotSupported("list storage spaces") } diff --git a/pkg/storage/fs/owncloudsql/owncloudsql.go b/pkg/storage/fs/owncloudsql/owncloudsql.go index 64803fc9458..ca38b13afe1 100644 --- a/pkg/storage/fs/owncloudsql/owncloudsql.go +++ b/pkg/storage/fs/owncloudsql/owncloudsql.go @@ -1950,7 +1950,7 @@ func (fs *owncloudsqlfs) HashFile(path string) (string, string, string, error) { } } -func (fs *owncloudsqlfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, _ map[string]struct{}) ([]*provider.StorageSpace, error) { +func (fs *owncloudsqlfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { // TODO(corby): Implement return nil, errtypes.NotSupported("list storage spaces") } diff --git a/pkg/storage/fs/s3/s3.go b/pkg/storage/fs/s3/s3.go index 063800326e4..76799b90478 100644 --- a/pkg/storage/fs/s3/s3.go +++ b/pkg/storage/fs/s3/s3.go @@ -702,7 +702,7 @@ func (fs *s3FS) RestoreRecycleItem(ctx context.Context, basePath, key, relativeP return errtypes.NotSupported("restore recycle") } -func (fs *s3FS) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, _ map[string]struct{}) ([]*provider.StorageSpace, error) { +func (fs *s3FS) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { return nil, errtypes.NotSupported("list storage spaces") } diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go index 4256d10cdf3..ae5cba3428f 100644 --- a/pkg/storage/storage.go +++ b/pkg/storage/storage.go @@ -62,7 +62,7 @@ type FS interface { GetLock(ctx context.Context, ref *provider.Reference) (*provider.Lock, error) RefreshLock(ctx context.Context, ref *provider.Reference, lock *provider.Lock) error Unlock(ctx context.Context, ref *provider.Reference) error - ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, permissions map[string]struct{}) ([]*provider.StorageSpace, error) + ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error) UpdateStorageSpace(ctx context.Context, req *provider.UpdateStorageSpaceRequest) (*provider.UpdateStorageSpaceResponse, error) } diff --git a/pkg/storage/utils/decomposedfs/decomposedfs.go b/pkg/storage/utils/decomposedfs/decomposedfs.go index 5e40163e6ed..b3ac9e138c4 100644 --- a/pkg/storage/utils/decomposedfs/decomposedfs.go +++ b/pkg/storage/utils/decomposedfs/decomposedfs.go @@ -39,6 +39,7 @@ import ( ctxpkg "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/logger" + "github.com/cs3org/reva/pkg/sharedconf" "github.com/cs3org/reva/pkg/storage" "github.com/cs3org/reva/pkg/storage/utils/chunking" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node" @@ -101,6 +102,8 @@ func NewDefault(m map[string]interface{}, bs tree.Blobstore) (storage.FS, error) lu.Options = o tp := tree.New(o.Root, o.TreeTimeAccounting, o.TreeSizeAccounting, lu, bs) + + o.GatewayAddr = sharedconf.GetGatewaySVC(o.GatewayAddr) return New(o, lu, p, tp) } diff --git a/pkg/storage/utils/decomposedfs/options/options.go b/pkg/storage/utils/decomposedfs/options/options.go index 181d20f19e5..892b185a22f 100644 --- a/pkg/storage/utils/decomposedfs/options/options.go +++ b/pkg/storage/utils/decomposedfs/options/options.go @@ -53,6 +53,8 @@ type Options struct { Owner string `mapstructure:"owner"` OwnerIDP string `mapstructure:"owner_idp"` OwnerType string `mapstructure:"owner_type"` + + GatewayAddr string `mapstructure:"gateway_addr"` } // New returns a new Options instance for the given configuration diff --git a/pkg/storage/utils/decomposedfs/spaces.go b/pkg/storage/utils/decomposedfs/spaces.go index b34bb57f656..989ae0c1243 100644 --- a/pkg/storage/utils/decomposedfs/spaces.go +++ b/pkg/storage/utils/decomposedfs/spaces.go @@ -28,12 +28,14 @@ import ( "strings" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + permissionsv1beta1 "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1" v1beta11 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" ocsconv "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" "github.com/cs3org/reva/pkg/appctx" ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node" "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs" "github.com/cs3org/reva/pkg/utils" @@ -156,7 +158,7 @@ func (fs *Decomposedfs) CreateStorageSpace(ctx context.Context, req *provider.Cr // The list can be filtered by space type or space id. // Spaces are persisted with symlinks in /spaces// pointing to ../../nodes/, the root node of the space // The spaceid is a concatenation of storageid + "!" + nodeid -func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, permissions map[string]struct{}) ([]*provider.StorageSpace, error) { +func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { // TODO check filters // TODO when a space symlink is broken delete the space for cleanup @@ -200,6 +202,28 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide return spaces, nil } + client, err := pool.GetGatewayServiceClient(fs.o.GatewayAddr) + if err != nil { + return nil, err + } + + checkRes, err := client.CheckPermission(ctx, &permissionsv1beta1.CheckPermissionRequest{ + Permission: "list-all-spaces", + SubjectRef: &permissionsv1beta1.SubjectReference{ + Spec: &permissionsv1beta1.SubjectReference_UserId{ + UserId: u.Id, + }, + }, + }) + if err != nil { + return nil, err + } + + canListAllSpaces := false + if checkRes.Status.Code == v1beta11.Code_CODE_OK { + canListAllSpaces = true + } + for i := range matches { // always read link in case storage space id != node id if target, err := os.Readlink(matches[i]); err != nil { @@ -226,7 +250,7 @@ func (fs *Decomposedfs) ListStorageSpaces(ctx context.Context, filter []*provide } // TODO apply more filters - space, err := fs.storageSpaceFromNode(ctx, n, matches[i], spaceType, permissions) + space, err := fs.storageSpaceFromNode(ctx, n, matches[i], spaceType, canListAllSpaces) if err != nil { appctx.GetLogger(ctx).Error().Err(err).Interface("node", n).Msg("could not convert to storage space") continue @@ -329,7 +353,7 @@ func (fs *Decomposedfs) createStorageSpace(ctx context.Context, spaceType, nodeI return nil } -func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, node *node.Node, nodePath, spaceType string, permissions map[string]struct{}) (*provider.StorageSpace, error) { +func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, node *node.Node, nodePath, spaceType string, canListAllSpaces bool) (*provider.StorageSpace, error) { owner, err := node.Owner() if err != nil { return nil, err @@ -357,13 +381,14 @@ func (fs *Decomposedfs) storageSpaceFromNode(ctx context.Context, node *node.Nod user := ctxpkg.ContextMustGetUser(ctx) // filter out spaces user cannot access (currently based on stat permission) - _, canListAllSpaces := permissions["list-all-spaces"] - p, err := node.ReadUserPermissions(ctx, user) - if err != nil { - return nil, err - } - if !(canListAllSpaces || p.Stat) { - return nil, errors.New("user is not allowed to Stat the space") + if !canListAllSpaces { + p, err := node.ReadUserPermissions(ctx, user) + if err != nil { + return nil, err + } + if !p.Stat { + return nil, errors.New("user is not allowed to Stat the space") + } } space.Owner = &userv1beta1.User{ // FIXME only return a UserID, not a full blown user object diff --git a/pkg/storage/utils/eosfs/eosfs.go b/pkg/storage/utils/eosfs/eosfs.go index 89cd109cdc7..c95cb0f7bf3 100644 --- a/pkg/storage/utils/eosfs/eosfs.go +++ b/pkg/storage/utils/eosfs/eosfs.go @@ -1617,7 +1617,7 @@ func (fs *eosfs) RestoreRecycleItem(ctx context.Context, basePath, key, relative return fs.c.RestoreDeletedEntry(ctx, auth, key) } -func (fs *eosfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, _ map[string]struct{}) ([]*provider.StorageSpace, error) { +func (fs *eosfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { return nil, errtypes.NotSupported("list storage spaces") } diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index af70f404e36..1cfe35c90bf 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -1286,7 +1286,7 @@ func (fs *localfs) RestoreRecycleItem(ctx context.Context, basePath, key, relati return fs.propagate(ctx, localRestorePath) } -func (fs *localfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter, _ map[string]struct{}) ([]*provider.StorageSpace, error) { +func (fs *localfs) ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { return nil, errtypes.NotSupported("list storage spaces") } diff --git a/tests/oc-integration-tests/drone/frontend.toml b/tests/oc-integration-tests/drone/frontend.toml index 84ed3b43f78..ce4f7f90d36 100644 --- a/tests/oc-integration-tests/drone/frontend.toml +++ b/tests/oc-integration-tests/drone/frontend.toml @@ -59,6 +59,7 @@ files_namespace = "/users" webdav_namespace = "/home" [http.services.ocs] +storage_registry_svc = "localhost:19000" [http.services.ocs.capabilities.capabilities.core.status] version = "10.0.11.5" diff --git a/tests/oc-integration-tests/local/frontend.toml b/tests/oc-integration-tests/local/frontend.toml index 97536ddd717..8111b454fcb 100644 --- a/tests/oc-integration-tests/local/frontend.toml +++ b/tests/oc-integration-tests/local/frontend.toml @@ -50,6 +50,7 @@ webdav_namespace = "/home" # serve /ocs which contains the sharing and user provisioning api of owncloud classic [http.services.ocs] +storage_registry_svc = "localhost:19000" [http.services.ocs.capabilities.capabilities.core.status] version = "10.0.11.5"