diff --git a/cmd/revad/runtime/loader.go b/cmd/revad/runtime/loader.go index f01fc71a2f..eaf60bbb92 100644 --- a/cmd/revad/runtime/loader.go +++ b/cmd/revad/runtime/loader.go @@ -43,13 +43,13 @@ import ( _ "github.com/cs3org/reva/pkg/ocm/share/repository/loader" _ "github.com/cs3org/reva/pkg/permission/manager/loader" _ "github.com/cs3org/reva/pkg/preferences/loader" + _ "github.com/cs3org/reva/pkg/projects/manager/loader" _ "github.com/cs3org/reva/pkg/prom/loader" _ "github.com/cs3org/reva/pkg/publicshare/manager/loader" _ "github.com/cs3org/reva/pkg/rhttp/datatx/manager/loader" _ "github.com/cs3org/reva/pkg/share/cache/loader" _ "github.com/cs3org/reva/pkg/share/cache/warmup/loader" _ "github.com/cs3org/reva/pkg/share/manager/loader" - _ "github.com/cs3org/reva/pkg/spaces/manager/loader" _ "github.com/cs3org/reva/pkg/storage/favorite/loader" _ "github.com/cs3org/reva/pkg/storage/fs/loader" _ "github.com/cs3org/reva/pkg/storage/registry/loader" diff --git a/internal/grpc/services/spacesregistry/spacesregistry.go b/internal/grpc/services/spacesregistry/spacesregistry.go index c44230e30d..74874d8dd9 100644 --- a/internal/grpc/services/spacesregistry/spacesregistry.go +++ b/internal/grpc/services/spacesregistry/spacesregistry.go @@ -20,17 +20,22 @@ package spacesregistry import ( "context" - "encoding/base32" "errors" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/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/appctx" "github.com/cs3org/reva/pkg/errtypes" "github.com/cs3org/reva/pkg/plugin" + "github.com/cs3org/reva/pkg/projects" + "github.com/cs3org/reva/pkg/projects/manager/registry" "github.com/cs3org/reva/pkg/rgrpc" "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/sharedconf" "github.com/cs3org/reva/pkg/spaces" - "github.com/cs3org/reva/pkg/spaces/manager/registry" "github.com/cs3org/reva/pkg/utils" "github.com/cs3org/reva/pkg/utils/cfg" "google.golang.org/grpc" @@ -55,8 +60,9 @@ func (c *config) ApplyDefaults() { } type service struct { - c *config - spaces spaces.Manager + c *config + projects projects.Catalogue + gw gateway.GatewayAPIClient } func New(ctx context.Context, m map[string]interface{}) (rgrpc.Service, error) { @@ -68,14 +74,21 @@ func New(ctx context.Context, m map[string]interface{}) (rgrpc.Service, error) { if err != nil { return nil, err } + + client, err := pool.GetGatewayServiceClient(pool.Endpoint(sharedconf.GetGatewaySVC(""))) + if err != nil { + return nil, err + } + svc := service{ - c: &c, - spaces: s, + c: &c, + projects: s, + gw: client, } return &svc, nil } -func getSpacesDriver(ctx context.Context, driver string, cfg map[string]map[string]any) (spaces.Manager, error) { +func getSpacesDriver(ctx context.Context, driver string, cfg map[string]map[string]any) (projects.Catalogue, error) { if f, ok := registry.NewFuncs[driver]; ok { return f(ctx, cfg[driver]) } @@ -88,21 +101,90 @@ func (s *service) CreateStorageSpace(ctx context.Context, req *provider.CreateSt func (s *service) ListStorageSpaces(ctx context.Context, req *provider.ListStorageSpacesRequest) (*provider.ListStorageSpacesResponse, error) { user := appctx.ContextMustGetUser(ctx) - spaces, err := s.spaces.ListSpaces(ctx, user, req.Filters) - if err != nil { - return &provider.ListStorageSpacesResponse{ - Status: status.NewInternal(ctx, err, "error listing storage spaces"), - }, nil + filters := req.Filters + + sp := []*provider.StorageSpace{} + if len(filters) == 0 { + homes, err := s.listSpacesByType(ctx, user, spaces.SpaceTypeHome) + if err != nil { + return &provider.ListStorageSpacesResponse{Status: status.NewInternal(ctx, err, err.Error())}, nil + } + sp = append(sp, homes...) + + projects, err := s.listSpacesByType(ctx, user, spaces.SpaceTypeProject) + if err != nil { + return &provider.ListStorageSpacesResponse{Status: status.NewInternal(ctx, err, err.Error())}, nil + } + sp = append(sp, projects...) } - for _, s := range spaces { - s.Id = &provider.StorageSpaceId{ - OpaqueId: base32.StdEncoding.EncodeToString([]byte(s.RootInfo.Path)), + for _, filter := range filters { + switch filter.Type { + case provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE: + spaces, err := s.listSpacesByType(ctx, user, spaces.SpaceType(filter.Term.(*provider.ListStorageSpacesRequest_Filter_SpaceType).SpaceType)) + if err != nil { + return &provider.ListStorageSpacesResponse{Status: status.NewInternal(ctx, err, err.Error())}, nil + } + sp = append(sp, spaces...) + default: + return nil, errtypes.NotSupported("filter not supported") } } - return &provider.ListStorageSpacesResponse{ - Status: status.NewOK(ctx), - StorageSpaces: spaces, + return &provider.ListStorageSpacesResponse{Status: status.NewOK(ctx), StorageSpaces: sp}, nil +} + +func (s *service) listSpacesByType(ctx context.Context, user *userpb.User, spaceType spaces.SpaceType) ([]*provider.StorageSpace, error) { + sp := []*provider.StorageSpace{} + + if spaceType == spaces.SpaceTypeHome { + space, err := s.userSpace(ctx, user) + if err != nil { + return nil, err + } + if space != nil { + sp = append(sp, space) + } + } else if spaceType == spaces.SpaceTypeProject { + projects, err := s.projects.ListProjects(ctx, user) + if err != nil { + return nil, err + } + sp = append(sp, projects...) + } + + return sp, nil +} + +func (s *service) userSpace(ctx context.Context, user *userpb.User) (*provider.StorageSpace, error) { + if utils.UserIsLightweight(user) { + return nil, nil // lightweight accounts and federated do not have a user space + } + + home, err := s.gw.GetHome(ctx, &provider.GetHomeRequest{}) + if err != nil { + return nil, err + } + + stat, err := s.gw.Stat(ctx, &provider.StatRequest{ + Ref: &provider.Reference{ + Path: home.Path, + }, + }) + if err != nil { + return nil, err + } + + return &provider.StorageSpace{ + Id: &provider.StorageSpaceId{ + OpaqueId: spaces.EncodeSpaceID(stat.Info.Id.StorageId, home.Path), + }, + Owner: user, + Name: user.Username, + SpaceType: spaces.SpaceTypeHome.AsString(), + RootInfo: &provider.ResourceInfo{ + PermissionSet: conversions.NewManagerRole().CS3ResourcePermissions(), + Path: home.Path, + }, }, nil } diff --git a/pkg/spaces/manager/loader/loader.go b/pkg/projects/manager/loader/loader.go similarity index 93% rename from pkg/spaces/manager/loader/loader.go rename to pkg/projects/manager/loader/loader.go index a019c6e728..3c770c5510 100644 --- a/pkg/spaces/manager/loader/loader.go +++ b/pkg/projects/manager/loader/loader.go @@ -20,6 +20,6 @@ package loader import ( // Load core spacews backends. - _ "github.com/cs3org/reva/pkg/spaces/manager/memory" + _ "github.com/cs3org/reva/pkg/projects/manager/memory" // Add your own here. ) diff --git a/pkg/projects/manager/memory/memory.go b/pkg/projects/manager/memory/memory.go new file mode 100644 index 0000000000..ef37d228c1 --- /dev/null +++ b/pkg/projects/manager/memory/memory.go @@ -0,0 +1,115 @@ +// Copyright 2018-2023 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 memory + +import ( + "context" + "slices" + + "github.com/cs3org/reva/pkg/projects" + "github.com/cs3org/reva/pkg/projects/manager/registry" + "github.com/cs3org/reva/pkg/spaces" + "github.com/cs3org/reva/pkg/utils/cfg" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + conversions "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" +) + +func init() { + registry.Register("memory", New) +} + +type SpaceDescription struct { + StorageID string `mapstructure:"storage_id" validate:"required"` + Path string `mapstructure:"path" validate:"required"` + Name string `mapstructure:"name" validate:"required"` + Type string `mapstructure:"type" validate:"required"` + Owner string `mapstructure:"owner" validate:"required"` + Readers string `mapstructure:"readers" validate:"required"` + Writers string `mapstructure:"writers" validate:"required"` + Admins string `mapstructure:"admins" validate:"required"` +} + +type Config struct { + Spaces []SpaceDescription `mapstructure:"spaces"` +} + +type service struct { + c *Config +} + +func New(ctx context.Context, m map[string]any) (projects.Catalogue, error) { + var c Config + if err := cfg.Decode(m, &c); err != nil { + return nil, err + } + return NewWithConfig(ctx, &c) +} + +func NewWithConfig(ctx context.Context, c *Config) (projects.Catalogue, error) { + return &service{c: c}, nil +} + +func (s *service) StoreProject(ctx context.Context, owner *userpb.UserId, path, name string, quota *provider.Quota) error { + panic("not yet implemented") +} + +func (s *service) ListProjects(ctx context.Context, user *userpb.User) ([]*provider.StorageSpace, error) { + projects := []*provider.StorageSpace{} + for _, space := range s.c.Spaces { + if perms, ok := projectBelongToUser(user, &space); ok { + projects = append(projects, &provider.StorageSpace{ + Id: &provider.StorageSpaceId{ + OpaqueId: spaces.EncodeSpaceID(space.StorageID, space.Path), + }, + Owner: &userpb.User{ + Id: &userpb.UserId{ + OpaqueId: space.Owner, + }, + }, + Name: space.Name, + SpaceType: spaces.SpaceTypeProject.AsString(), + RootInfo: &provider.ResourceInfo{ + Path: space.Path, + PermissionSet: perms, + }, + }) + } + } + return projects, nil +} + +func projectBelongToUser(user *userpb.User, project *SpaceDescription) (*provider.ResourcePermissions, bool) { + if user.Id.OpaqueId == project.Owner { + return conversions.NewManagerRole().CS3ResourcePermissions(), true + } + if slices.Contains(user.Groups, project.Admins) { + return conversions.NewManagerRole().CS3ResourcePermissions(), true + } + if slices.Contains(user.Groups, project.Writers) { + return conversions.NewEditorRole().CS3ResourcePermissions(), true + } + if slices.Contains(user.Groups, project.Readers) { + return conversions.NewViewerRole().CS3ResourcePermissions(), true + } + return nil, false +} + +var _ projects.Catalogue = (*service)(nil) diff --git a/pkg/projects/manager/memory/memory_test.go b/pkg/projects/manager/memory/memory_test.go new file mode 100644 index 0000000000..b6d042fceb --- /dev/null +++ b/pkg/projects/manager/memory/memory_test.go @@ -0,0 +1,205 @@ +// Copyright 2018-2023 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 memory_test + +// import ( +// "context" +// "slices" +// "testing" + +// userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" +// provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +// "github.com/cs3org/reva/pkg/spaces" +// "github.com/cs3org/reva/pkg/spaces/manager/memory" +// "github.com/cs3org/reva/pkg/utils" +// ) + +// var einstein = &userpb.User{ +// Id: &userpb.UserId{ +// Idp: "example.org", +// OpaqueId: "einstein", +// Type: userpb.UserType_USER_TYPE_PRIMARY, +// }, +// Username: "einstein", +// Groups: []string{"cernbox-projects-cernbox-admins", "violin-haters", "physics-lovers"}, +// } + +// var marie = &userpb.User{ +// Id: &userpb.UserId{ +// Idp: "example.org", +// OpaqueId: "marie", +// Type: userpb.UserType_USER_TYPE_PRIMARY, +// }, +// Username: "marie", +// Groups: []string{"radium-lovers", "cernbox-projects-eos-readers", "physics-lovers"}, +// } + +// var lightweight = &userpb.User{ +// Id: &userpb.UserId{ +// Idp: "something-external.org", +// OpaqueId: "0123456789", +// Type: userpb.UserType_USER_TYPE_LIGHTWEIGHT, +// }, +// Username: "0123456789", +// Groups: []string{"radium-lovers", "cernbox-projects-eos-readers"}, +// } + +// var projectDescription1 = memory.SpaceDescription{ +// Path: "/eos/project/c/cernbox", +// Name: "cernbox", +// Owner: "cboxsvc", +// Readers: "cernbox-projects-cernbox-readers", +// Writers: "cernbox-projects-cernbox-writers", +// Admins: "cernbox-projects-cernbox-admins", +// } +// var projectDescription2 = memory.SpaceDescription{ +// Path: "/eos/project/e/eos", +// Name: "eos", +// Owner: "eossvc", +// Readers: "cernbox-projects-eos-readers", +// Writers: "cernbox-projects-eos-writers", +// Admins: "cernbox-projects-eos-admins", +// } + +// var projectSpace1 = &provider.StorageSpace{ +// Id: &provider.StorageSpaceId{OpaqueId: projectDescription1.ID}, +// Owner: &userpb.User{Id: &userpb.UserId{OpaqueId: projectDescription1.Owner}}, +// Name: projectDescription1.Name, +// SpaceType: spaces.SpaceTypeProject.AsString(), +// RootInfo: &provider.ResourceInfo{Path: projectDescription1.Path}, +// } +// var projectSpace2 = &provider.StorageSpace{ +// Id: &provider.StorageSpaceId{OpaqueId: projectDescription2.ID}, +// Owner: &userpb.User{Id: &userpb.UserId{OpaqueId: projectDescription2.Owner}}, +// Name: projectDescription2.Name, +// SpaceType: spaces.SpaceTypeProject.AsString(), +// RootInfo: &provider.ResourceInfo{Path: projectDescription2.Path}, +// } + +// func TestListSpaces(t *testing.T) { +// tests := []struct { +// config *memory.Config +// user *userpb.User +// expected []*provider.StorageSpace +// }{ +// { +// config: &memory.Config{ +// UserSpace: "/home", +// }, +// user: einstein, +// expected: []*provider.StorageSpace{ +// { +// Id: &provider.StorageSpaceId{OpaqueId: "/home"}, +// Owner: einstein, +// Name: einstein.Username, +// SpaceType: spaces.SpaceTypeHome.AsString(), +// RootInfo: &provider.ResourceInfo{ +// Path: "/home", +// }, +// }, +// }, +// }, +// { +// config: &memory.Config{ +// UserSpace: "/home", +// }, +// user: lightweight, +// expected: []*provider.StorageSpace{}, +// }, +// { +// config: &memory.Config{ +// UserSpace: "/home/{{ .Username }}", +// Spaces: []memory.SpaceDescription{ +// projectDescription1, +// projectDescription2, +// }, +// }, +// user: einstein, +// expected: []*provider.StorageSpace{ +// { +// Id: &provider.StorageSpaceId{OpaqueId: "/home/einstein"}, +// Owner: einstein, +// Name: einstein.Username, +// SpaceType: spaces.SpaceTypeHome.AsString(), +// RootInfo: &provider.ResourceInfo{ +// Path: "/home/einstein", +// }, +// }, +// projectSpace1, +// }, +// }, +// { +// config: &memory.Config{ +// UserSpace: "/home/{{ .Username }}", +// Spaces: []memory.SpaceDescription{ +// projectDescription1, +// projectDescription2, +// }, +// }, +// user: marie, +// expected: []*provider.StorageSpace{ +// { +// Id: &provider.StorageSpaceId{OpaqueId: "/home/marie"}, +// Owner: marie, +// Name: marie.Username, +// SpaceType: spaces.SpaceTypeHome.AsString(), +// RootInfo: &provider.ResourceInfo{ +// Path: "/home/marie", +// }, +// }, +// projectSpace2, +// }, +// }, +// { +// config: &memory.Config{ +// UserSpace: "/home", +// Spaces: []memory.SpaceDescription{ +// projectDescription1, +// projectDescription2, +// }, +// }, +// user: lightweight, +// expected: []*provider.StorageSpace{ +// projectSpace2, +// }, +// }, +// } + +// for _, tt := range tests { +// s, err := memory.NewWithConfig(context.Background(), tt.config) +// if err != nil { +// t.Fatalf("got unexpected error creating new memory spaces provider: %+v", err) +// } + +// got, err := s.ListSpaces(context.Background(), tt.user, nil) +// if err != nil { +// t.Fatalf("got unexpected error getting list of spaces: %+v", err) +// } + +// if !slices.EqualFunc(tt.expected, got, func(s1, s2 *provider.StorageSpace) bool { +// return s1.Id != nil && s2.Id != nil && s1.Id.OpaqueId == s2.Id.OpaqueId && +// s1.Name == s2.Name && +// s1.SpaceType == s2.SpaceType && +// utils.UserEqual(s1.Owner.Id, s2.Owner.Id) && +// s1.RootInfo.Path == s2.RootInfo.Path +// }) { +// t.Fatalf("got different result. expected=%+v got=%+v", tt.expected, got) +// } +// } +// } diff --git a/pkg/spaces/manager/registry/registry.go b/pkg/projects/manager/registry/registry.go similarity index 75% rename from pkg/spaces/manager/registry/registry.go rename to pkg/projects/manager/registry/registry.go index fc63efb9f0..2b38ea32dc 100644 --- a/pkg/spaces/manager/registry/registry.go +++ b/pkg/projects/manager/registry/registry.go @@ -21,17 +21,17 @@ package registry import ( "context" - "github.com/cs3org/reva/pkg/spaces" + "github.com/cs3org/reva/pkg/projects" ) -// NewFunc is the function that space manager implementations +// NewFunc is the function that the projects' catalogues implementations // should register at init time. -type NewFunc func(context.Context, map[string]interface{}) (spaces.Manager, error) +type NewFunc func(context.Context, map[string]interface{}) (projects.Catalogue, error) -// NewFuncs is a map containing all the registered space managers. +// NewFuncs is a map containing all the registered projects' catalogues. var NewFuncs = map[string]NewFunc{} -// Register registers a new space manager new function. +// Register registers a new project catalogue new function. // Not safe for concurrent use. Safe for use from package init. func Register(name string, f NewFunc) { NewFuncs[name] = f diff --git a/pkg/projects/projects.go b/pkg/projects/projects.go new file mode 100644 index 0000000000..eab30d47b2 --- /dev/null +++ b/pkg/projects/projects.go @@ -0,0 +1,32 @@ +// Copyright 2018-2023 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 projects + +import ( + "context" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) + +// Catalogue is the interface that stores the project spaces. +type Catalogue interface { + StoreProject(ctx context.Context, owner *userpb.UserId, path, name string, quota *provider.Quota) error + ListProjects(ctx context.Context, user *userpb.User) ([]*provider.StorageSpace, error) +} diff --git a/pkg/spaces/manager/memory/memory.go b/pkg/spaces/manager/memory/memory.go deleted file mode 100644 index 1915a748ea..0000000000 --- a/pkg/spaces/manager/memory/memory.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2018-2023 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 memory - -import ( - "context" - "errors" - "slices" - - "github.com/cs3org/reva/pkg/errtypes" - "github.com/cs3org/reva/pkg/spaces" - "github.com/cs3org/reva/pkg/spaces/manager/registry" - "github.com/cs3org/reva/pkg/storage/utils/templates" - "github.com/cs3org/reva/pkg/utils" - "github.com/cs3org/reva/pkg/utils/cfg" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - conversions "github.com/cs3org/reva/internal/http/services/owncloud/ocs/conversions" -) - -func init() { - registry.Register("memory", New) -} - -type SpaceDescription struct { - ID string `mapstructure:"id" validate:"required"` - Path string `mapstructure:"path" validate:"required"` - Name string `mapstructure:"name" validate:"required"` - Type string `mapstructure:"type" validate:"required"` - Owner string `mapstructure:"owner" validate:"required"` - Readers string `mapstructure:"readers" validate:"required"` - Writers string `mapstructure:"writers" validate:"required"` - Admins string `mapstructure:"admins" validate:"required"` -} - -type Config struct { - Spaces []SpaceDescription `mapstructure:"spaces"` - UserSpace string `mapstructure:"user_space" validate:"required"` -} - -func (c *Config) ApplyDefaults() { - if c.UserSpace == "" { - c.UserSpace = "/home" - } -} - -type service struct { - c *Config -} - -func New(ctx context.Context, m map[string]any) (spaces.Manager, error) { - var c Config - if err := cfg.Decode(m, &c); err != nil { - return nil, err - } - return NewWithConfig(ctx, &c) -} - -func NewWithConfig(ctx context.Context, c *Config) (spaces.Manager, error) { - return &service{c: c}, nil -} - -func (s *service) StoreSpace(ctx context.Context, owner *userpb.UserId, path, name string, quota *provider.Quota) error { - return errors.New("not yet implemented") -} - -func (s *service) listSpacesByType(ctx context.Context, user *userpb.User, spaceType spaces.SpaceType) []*provider.StorageSpace { - sp := []*provider.StorageSpace{} - - if spaceType == spaces.SpaceTypeHome { - if space := s.userSpace(ctx, user); space != nil { - sp = append(sp, space) - } - } else if spaceType == spaces.SpaceTypeProject { - projects := s.projectSpaces(ctx, user) - sp = append(sp, projects...) - } - - return sp -} - -func (s *service) ListSpaces(ctx context.Context, user *userpb.User, filters []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) { - sp := []*provider.StorageSpace{} - if len(filters) == 0 { - sp = s.listSpacesByType(ctx, user, spaces.SpaceTypeHome) - sp = append(sp, s.listSpacesByType(ctx, user, spaces.SpaceTypeProject)...) - return sp, nil - } - - for _, filter := range filters { - switch filter.Type { - case provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE: - sp = append(sp, s.listSpacesByType(ctx, user, spaces.SpaceType(filter.Term.(*provider.ListStorageSpacesRequest_Filter_SpaceType).SpaceType))...) - default: - return nil, errtypes.NotSupported("filter not supported") - } - } - return sp, nil -} - -func (s *service) userSpace(ctx context.Context, user *userpb.User) *provider.StorageSpace { - if utils.UserIsLightweight(user) { - return nil // lightweight accounts and federated do not have a user space - } - path := templates.WithUser(user, s.c.UserSpace) - return &provider.StorageSpace{ - Id: &provider.StorageSpaceId{ - OpaqueId: path, - }, - Owner: user, - Name: user.Username, - SpaceType: spaces.SpaceTypeHome.AsString(), - RootInfo: &provider.ResourceInfo{ - PermissionSet: conversions.NewManagerRole().CS3ResourcePermissions(), - Path: path, - }, - } -} - -func (s *service) projectSpaces(ctx context.Context, user *userpb.User) []*provider.StorageSpace { - projects := []*provider.StorageSpace{} - for _, space := range s.c.Spaces { - if perms, ok := projectBelongToUser(user, &space); ok { - projects = append(projects, &provider.StorageSpace{ - Id: &provider.StorageSpaceId{ - OpaqueId: space.ID, - }, - Owner: &userpb.User{ - Id: &userpb.UserId{ - OpaqueId: space.Owner, - }, - }, - Name: space.Name, - SpaceType: spaces.SpaceTypeProject.AsString(), - RootInfo: &provider.ResourceInfo{ - Path: space.Path, - PermissionSet: perms, - }, - }) - } - } - return projects -} - -func projectBelongToUser(user *userpb.User, project *SpaceDescription) (*provider.ResourcePermissions, bool) { - if user.Id.OpaqueId == project.Owner { - return conversions.NewManagerRole().CS3ResourcePermissions(), true - } - if slices.Contains(user.Groups, project.Admins) { - return conversions.NewManagerRole().CS3ResourcePermissions(), true - } - if slices.Contains(user.Groups, project.Writers) { - return conversions.NewEditorRole().CS3ResourcePermissions(), true - } - if slices.Contains(user.Groups, project.Readers) { - return conversions.NewViewerRole().CS3ResourcePermissions(), true - } - return nil, false -} - -func (s *service) UpdateSpace(ctx context.Context, space *provider.StorageSpace) error { - return errors.New("not yet implemented") -} - -func (s *service) DeleteSpace(ctx context.Context, spaceID *provider.StorageSpaceId) error { - return errors.New("not yet implemented") -} diff --git a/pkg/spaces/manager/memory/memory_test.go b/pkg/spaces/manager/memory/memory_test.go deleted file mode 100644 index fa8ce7425f..0000000000 --- a/pkg/spaces/manager/memory/memory_test.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2018-2023 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 memory_test - -import ( - "context" - "slices" - "testing" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/pkg/spaces" - "github.com/cs3org/reva/pkg/spaces/manager/memory" - "github.com/cs3org/reva/pkg/utils" -) - -var einstein = &userpb.User{ - Id: &userpb.UserId{ - Idp: "example.org", - OpaqueId: "einstein", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Username: "einstein", - Groups: []string{"cernbox-projects-cernbox-admins", "violin-haters", "physics-lovers"}, -} - -var marie = &userpb.User{ - Id: &userpb.UserId{ - Idp: "example.org", - OpaqueId: "marie", - Type: userpb.UserType_USER_TYPE_PRIMARY, - }, - Username: "marie", - Groups: []string{"radium-lovers", "cernbox-projects-eos-readers", "physics-lovers"}, -} - -var lightweight = &userpb.User{ - Id: &userpb.UserId{ - Idp: "something-external.org", - OpaqueId: "0123456789", - Type: userpb.UserType_USER_TYPE_LIGHTWEIGHT, - }, - Username: "0123456789", - Groups: []string{"radium-lovers", "cernbox-projects-eos-readers"}, -} - -var projectDescription1 = memory.SpaceDescription{ - ID: "cernbox", - Path: "/eos/project/c/cernbox", - Name: "cernbox", - Owner: "cboxsvc", - Readers: "cernbox-projects-cernbox-readers", - Writers: "cernbox-projects-cernbox-writers", - Admins: "cernbox-projects-cernbox-admins", -} -var projectDescription2 = memory.SpaceDescription{ - ID: "eos", - Path: "/eos/project/e/eos", - Name: "eos", - Owner: "eossvc", - Readers: "cernbox-projects-eos-readers", - Writers: "cernbox-projects-eos-writers", - Admins: "cernbox-projects-eos-admins", -} - -var projectSpace1 = &provider.StorageSpace{ - Id: &provider.StorageSpaceId{OpaqueId: projectDescription1.ID}, - Owner: &userpb.User{Id: &userpb.UserId{OpaqueId: projectDescription1.Owner}}, - Name: projectDescription1.Name, - SpaceType: spaces.SpaceTypeProject.AsString(), - RootInfo: &provider.ResourceInfo{Path: projectDescription1.Path}, -} -var projectSpace2 = &provider.StorageSpace{ - Id: &provider.StorageSpaceId{OpaqueId: projectDescription2.ID}, - Owner: &userpb.User{Id: &userpb.UserId{OpaqueId: projectDescription2.Owner}}, - Name: projectDescription2.Name, - SpaceType: spaces.SpaceTypeProject.AsString(), - RootInfo: &provider.ResourceInfo{Path: projectDescription2.Path}, -} - -func TestListSpaces(t *testing.T) { - tests := []struct { - config *memory.Config - user *userpb.User - expected []*provider.StorageSpace - }{ - { - config: &memory.Config{ - UserSpace: "/home", - }, - user: einstein, - expected: []*provider.StorageSpace{ - { - Id: &provider.StorageSpaceId{OpaqueId: "/home"}, - Owner: einstein, - Name: einstein.Username, - SpaceType: spaces.SpaceTypeHome.AsString(), - RootInfo: &provider.ResourceInfo{ - Path: "/home", - }, - }, - }, - }, - { - config: &memory.Config{ - UserSpace: "/home", - }, - user: lightweight, - expected: []*provider.StorageSpace{}, - }, - { - config: &memory.Config{ - UserSpace: "/home/{{ .Username }}", - Spaces: []memory.SpaceDescription{ - projectDescription1, - projectDescription2, - }, - }, - user: einstein, - expected: []*provider.StorageSpace{ - { - Id: &provider.StorageSpaceId{OpaqueId: "/home/einstein"}, - Owner: einstein, - Name: einstein.Username, - SpaceType: spaces.SpaceTypeHome.AsString(), - RootInfo: &provider.ResourceInfo{ - Path: "/home/einstein", - }, - }, - projectSpace1, - }, - }, - { - config: &memory.Config{ - UserSpace: "/home/{{ .Username }}", - Spaces: []memory.SpaceDescription{ - projectDescription1, - projectDescription2, - }, - }, - user: marie, - expected: []*provider.StorageSpace{ - { - Id: &provider.StorageSpaceId{OpaqueId: "/home/marie"}, - Owner: marie, - Name: marie.Username, - SpaceType: spaces.SpaceTypeHome.AsString(), - RootInfo: &provider.ResourceInfo{ - Path: "/home/marie", - }, - }, - projectSpace2, - }, - }, - { - config: &memory.Config{ - UserSpace: "/home", - Spaces: []memory.SpaceDescription{ - projectDescription1, - projectDescription2, - }, - }, - user: lightweight, - expected: []*provider.StorageSpace{ - projectSpace2, - }, - }, - } - - for _, tt := range tests { - s, err := memory.NewWithConfig(context.Background(), tt.config) - if err != nil { - t.Fatalf("got unexpected error creating new memory spaces provider: %+v", err) - } - - got, err := s.ListSpaces(context.Background(), tt.user, nil) - if err != nil { - t.Fatalf("got unexpected error getting list of spaces: %+v", err) - } - - if !slices.EqualFunc(tt.expected, got, func(s1, s2 *provider.StorageSpace) bool { - return s1.Id != nil && s2.Id != nil && s1.Id.OpaqueId == s2.Id.OpaqueId && - s1.Name == s2.Name && - s1.SpaceType == s2.SpaceType && - utils.UserEqual(s1.Owner.Id, s2.Owner.Id) && - s1.RootInfo.Path == s2.RootInfo.Path - }) { - t.Fatalf("got different result. expected=%+v got=%+v", tt.expected, got) - } - } -} diff --git a/pkg/spaces/spaces.go b/pkg/spaces/spaces.go index 3f26d1c993..19a13c705e 100644 --- a/pkg/spaces/spaces.go +++ b/pkg/spaces/spaces.go @@ -1,38 +1,5 @@ -// Copyright 2018-2023 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 spaces -import ( - "context" - - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" -) - -// Manager is the interface that stores the spaces. -type Manager interface { - StoreSpace(ctx context.Context, owner *userpb.UserId, path, name string, quota *provider.Quota) error - ListSpaces(ctx context.Context, user *userpb.User, filters []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error) - UpdateSpace(ctx context.Context, space *provider.StorageSpace) error - DeleteSpace(ctx context.Context, spaceID *provider.StorageSpaceId) error -} - type SpaceType string const ( diff --git a/pkg/spaces/utils/utils.go b/pkg/spaces/utils.go similarity index 83% rename from pkg/spaces/utils/utils.go rename to pkg/spaces/utils.go index 7b20953741..e69162d16b 100644 --- a/pkg/spaces/utils/utils.go +++ b/pkg/spaces/utils.go @@ -1,4 +1,4 @@ -package utils +package spaces import ( "encoding/base32" @@ -45,12 +45,27 @@ func DecodeResourceID(raw string) (storageID, path, itemID string, ok bool) { // EncodeResourceID encodes the provided resource ID as a string, // in the format $!. func EncodeResourceID(r *provider.ResourceId) string { + if r.OpaqueId == "" { + panic("opaque id cannot be empty") + } + // if r.SpaceId == "" { + // panic("space id cannot be empty") + // } + if r.StorageId == "" { + panic("storage id cannot be empty") + } return fmt.Sprintf("%s$%s!%s", r.StorageId, r.SpaceId, r.OpaqueId) } // EncodeSpaceID encodes storage ID and path to create a space ID, // in the format $). func EncodeSpaceID(storageID, path string) string { + if storageID == "" { + panic("storage id cannot be empty") + } + if path == "" { + panic("path cannot be empty") + } encodedPath := base32.StdEncoding.EncodeToString([]byte(path)) return fmt.Sprintf("%s$%s", storageID, encodedPath) }