diff --git a/changelog/unreleased/preferences-refactor.md b/changelog/unreleased/preferences-refactor.md new file mode 100644 index 0000000000..a15d1fbcdf --- /dev/null +++ b/changelog/unreleased/preferences-refactor.md @@ -0,0 +1,7 @@ +Enhancement: Preferences driver refactor and cbox sql implementation + +This PR uses the updated CS3APIs which accepts a namespace in addition to a +single string key to recognize a user preference. It also refactors the GRPC +service to support multiple drivers and adds the cbox SQL implementation. + +https://github.com/cs3org/reva/pull/2696 \ No newline at end of file diff --git a/cmd/reva/preferences.go b/cmd/reva/preferences.go index 8a5f86e0a7..51471950bd 100644 --- a/cmd/reva/preferences.go +++ b/cmd/reva/preferences.go @@ -40,6 +40,7 @@ var preferencesCommand = func() *command { subcommand := cmd.Args()[0] key := cmd.Args()[1] + ns := cmd.Args()[2] client, err := getClient() if err != nil { @@ -50,12 +51,15 @@ var preferencesCommand = func() *command { switch subcommand { case "set": - if cmd.NArg() < 3 { + if cmd.NArg() < 4 { return errors.New("Invalid arguments: " + cmd.Usage()) } - value := cmd.Args()[2] + value := cmd.Args()[3] req := &preferences.SetKeyRequest{ - Key: key, + Key: &preferences.PreferenceKey{ + Namespace: ns, + Key: key, + }, Val: value, } @@ -70,7 +74,10 @@ var preferencesCommand = func() *command { case "get": req := &preferences.GetKeyRequest{ - Key: key, + Key: &preferences.PreferenceKey{ + Namespace: ns, + Key: key, + }, } res, err := client.GetKey(ctx, req) diff --git a/cmd/revad/runtime/loader.go b/cmd/revad/runtime/loader.go index 2dabdd5029..fc917052a9 100644 --- a/cmd/revad/runtime/loader.go +++ b/cmd/revad/runtime/loader.go @@ -40,6 +40,7 @@ import ( _ "github.com/cs3org/reva/pkg/ocm/provider/authorizer/loader" _ "github.com/cs3org/reva/pkg/ocm/share/manager/loader" _ "github.com/cs3org/reva/pkg/permission/manager/loader" + _ "github.com/cs3org/reva/pkg/preferences/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" diff --git a/go.mod b/go.mod index 7ab3d4f2a8..8d908a1b6b 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,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-20220214084335-d975ab5d6e65 + github.com/cs3org/go-cs3apis v0.0.0-20220330081745-2ad58f5932b9 github.com/cubewise-code/go-mime v0.0.0-20200519001935-8c5762b177d8 github.com/dgraph-io/ristretto v0.1.0 github.com/eventials/go-tus v0.0.0-20200718001131-45c7ec8f5d59 @@ -93,4 +93,4 @@ replace ( github.com/eventials/go-tus => github.com/andrewmostello/go-tus v0.0.0-20200314041820-904a9904af9a github.com/oleiade/reflections => github.com/oleiade/reflections v1.0.1 google.golang.org/grpc => google.golang.org/grpc v1.26.0 // temporary downgrade -) +) \ No newline at end of file diff --git a/go.sum b/go.sum index 74c94fc60b..e0c848fd95 100644 --- a/go.sum +++ b/go.sum @@ -210,10 +210,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.11/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-20211214102128-4e8745ab1654 h1:ha5tiuuFyDrwKUrVEc3TrRDFgTKVQ9NGDRmEP0PRPno= -github.com/cs3org/go-cs3apis v0.0.0-20211214102128-4e8745ab1654/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= -github.com/cs3org/go-cs3apis v0.0.0-20220214084335-d975ab5d6e65 h1:cee0dhBsF8KofV2TM52T41eOo1QLSgtgEZsjYmC5dhU= -github.com/cs3org/go-cs3apis v0.0.0-20220214084335-d975ab5d6e65/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY= +github.com/cs3org/go-cs3apis v0.0.0-20220330081745-2ad58f5932b9 h1:SuPu5Mc2mpz+J059XML+cMd0i5FZR4t/kROS3SaIsnU= +github.com/cs3org/go-cs3apis v0.0.0-20220330081745-2ad58f5932b9/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/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= diff --git a/internal/grpc/services/authregistry/authregistry.go b/internal/grpc/services/authregistry/authregistry.go index f3b3ab2262..a449a28be9 100644 --- a/internal/grpc/services/authregistry/authregistry.go +++ b/internal/grpc/services/authregistry/authregistry.go @@ -116,17 +116,17 @@ func (s *service) ListAuthProviders(ctx context.Context, req *registrypb.ListAut return res, nil } -func (s *service) GetAuthProvider(ctx context.Context, req *registrypb.GetAuthProviderRequest) (*registrypb.GetAuthProviderResponse, error) { +func (s *service) GetAuthProviders(ctx context.Context, req *registrypb.GetAuthProvidersRequest) (*registrypb.GetAuthProvidersResponse, error) { pinfo, err := s.reg.GetProvider(ctx, req.Type) if err != nil { - return ®istrypb.GetAuthProviderResponse{ + return ®istrypb.GetAuthProvidersResponse{ Status: status.NewInternal(ctx, err, "error getting auth provider for type: "+req.Type), }, nil } - res := ®istrypb.GetAuthProviderResponse{ - Status: status.NewOK(ctx), - Provider: pinfo, + res := ®istrypb.GetAuthProvidersResponse{ + Status: status.NewOK(ctx), + Providers: []*registrypb.ProviderInfo{pinfo}, } return res, nil } diff --git a/internal/grpc/services/gateway/authprovider.go b/internal/grpc/services/gateway/authprovider.go index b526188b13..fb2e522457 100644 --- a/internal/grpc/services/gateway/authprovider.go +++ b/internal/grpc/services/gateway/authprovider.go @@ -213,7 +213,7 @@ func (s *svc) findAuthProvider(ctx context.Context, authType string) (authpb.Pro return nil, err } - res, err := c.GetAuthProvider(ctx, ®istry.GetAuthProviderRequest{ + res, err := c.GetAuthProviders(ctx, ®istry.GetAuthProvidersRequest{ Type: authType, }) @@ -222,9 +222,9 @@ func (s *svc) findAuthProvider(ctx context.Context, authType string) (authpb.Pro return nil, err } - if res.Status.Code == rpc.Code_CODE_OK && res.Provider != nil { + if res.Status.Code == rpc.Code_CODE_OK && res.Providers != nil && len(res.Providers) > 0 { // TODO(labkode): check for capabilities here - c, err := pool.GetAuthProviderServiceClient(res.Provider.Address) + c, err := pool.GetAuthProviderServiceClient(res.Providers[0].Address) if err != nil { err = errors.Wrap(err, "gateway: error getting an auth provider client") return nil, err diff --git a/internal/grpc/services/preferences/preferences.go b/internal/grpc/services/preferences/preferences.go index c51e874cdb..6920fc7069 100644 --- a/internal/grpc/services/preferences/preferences.go +++ b/internal/grpc/services/preferences/preferences.go @@ -20,38 +20,73 @@ package preferences import ( "context" - "sync" "google.golang.org/grpc" - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - preferences "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" - ctxpkg "github.com/cs3org/reva/pkg/ctx" + preferencespb "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/preferences" + "github.com/cs3org/reva/pkg/preferences/registry" "github.com/cs3org/reva/pkg/rgrpc" "github.com/cs3org/reva/pkg/rgrpc/status" + "github.com/mitchellh/mapstructure" "github.com/pkg/errors" ) -type contextUserRequiredErr string - -func (err contextUserRequiredErr) Error() string { return string(err) } - func init() { rgrpc.Register("preferences", New) } -// m maps user to map of user preferences. -// m = map[userToken]map[key]value -var m = make(map[string]map[string]string) +type config struct { + Driver string `mapstructure:"driver"` + Drivers map[string]map[string]interface{} `mapstructure:"drivers"` +} + +func (c *config) init() { + if c.Driver == "" { + c.Driver = "memory" + } +} -var mutex = &sync.Mutex{} +type service struct { + conf *config + pm preferences.Manager +} -type service struct{} +func getPreferencesManager(c *config) (preferences.Manager, error) { + if f, ok := registry.NewFuncs[c.Driver]; ok { + return f(c.Drivers[c.Driver]) + } + return nil, errtypes.NotFound("driver not found: " + c.Driver) +} + +func parseConfig(m map[string]interface{}) (*config, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + err = errors.Wrap(err, "error decoding conf") + return nil, err + } + return c, nil +} // New returns a new PreferencesServiceServer func New(m map[string]interface{}, ss *grpc.Server) (rgrpc.Service, error) { - service := &service{} - return service, nil + c, err := parseConfig(m) + if err != nil { + return nil, err + } + + c.init() + + pm, err := getPreferencesManager(c) + if err != nil { + return nil, err + } + + return &service{ + conf: c, + pm: pm, + }, nil } func (s *service) Close() error { @@ -63,72 +98,36 @@ func (s *service) UnprotectedEndpoints() []string { } func (s *service) Register(ss *grpc.Server) { - preferences.RegisterPreferencesAPIServer(ss, s) + preferencespb.RegisterPreferencesAPIServer(ss, s) } -func getUser(ctx context.Context) (*userpb.User, error) { - u, ok := ctxpkg.ContextGetUser(ctx) - if !ok { - err := errors.Wrap(contextUserRequiredErr("userrequired"), "preferences: error getting user from ctx") - return nil, err - } - return u, nil -} - -func (s *service) SetKey(ctx context.Context, req *preferences.SetKeyRequest) (*preferences.SetKeyResponse, error) { - key := req.Key - value := req.Val - - u, err := getUser(ctx) +func (s *service) SetKey(ctx context.Context, req *preferencespb.SetKeyRequest) (*preferencespb.SetKeyResponse, error) { + err := s.pm.SetKey(ctx, req.Key.Key, req.Key.Namespace, req.Val) if err != nil { - err = errors.Wrap(err, "preferences: failed to call getUser") - return &preferences.SetKeyResponse{ - Status: status.NewUnauthenticated(ctx, err, "user not found or invalid"), - }, err + return &preferencespb.SetKeyResponse{ + Status: status.NewInternal(ctx, err, "error setting key"), + }, nil } - name := u.Username - - mutex.Lock() - defer mutex.Unlock() - if len(m[name]) == 0 { - m[name] = map[string]string{key: value} - } else { - usersettings := m[name] - usersettings[key] = value - } - - return &preferences.SetKeyResponse{ + return &preferencespb.SetKeyResponse{ Status: status.NewOK(ctx), }, nil } -func (s *service) GetKey(ctx context.Context, req *preferences.GetKeyRequest) (*preferences.GetKeyResponse, error) { - key := req.Key - u, err := getUser(ctx) +func (s *service) GetKey(ctx context.Context, req *preferencespb.GetKeyRequest) (*preferencespb.GetKeyResponse, error) { + val, err := s.pm.GetKey(ctx, req.Key.Key, req.Key.Namespace) if err != nil { - err = errors.Wrap(err, "preferences: failed to call getUser") - return &preferences.GetKeyResponse{ - Status: status.NewUnauthenticated(ctx, err, "user not found or invalid"), - }, err - } - - name := u.Username - - mutex.Lock() - defer mutex.Unlock() - if len(m[name]) != 0 { - if value, ok := m[name][key]; ok { - return &preferences.GetKeyResponse{ - Status: status.NewOK(ctx), - Val: value, - }, nil + st := status.NewInternal(ctx, err, "error retrieving key") + if _, ok := err.(errtypes.IsNotFound); ok { + st = status.NewNotFound(ctx, "key not found") } + return &preferencespb.GetKeyResponse{ + Status: st, + }, nil } - res := &preferences.GetKeyResponse{ - Status: status.NewNotFound(ctx, "key not found"), - Val: "", - } - return res, nil + return &preferencespb.GetKeyResponse{ + Status: status.NewOK(ctx), + Val: val, + }, nil } diff --git a/internal/http/services/appprovider/appprovider.go b/internal/http/services/appprovider/appprovider.go index ed101a0f3c..21ef5b3ee7 100644 --- a/internal/http/services/appprovider/appprovider.go +++ b/internal/http/services/appprovider/appprovider.go @@ -33,10 +33,10 @@ import ( "github.com/cs3org/reva/pkg/rgrpc/todo/pool" "github.com/cs3org/reva/pkg/rhttp" "github.com/cs3org/reva/pkg/rhttp/global" - "github.com/cs3org/reva/pkg/rhttp/router" "github.com/cs3org/reva/pkg/sharedconf" "github.com/cs3org/reva/pkg/utils" "github.com/cs3org/reva/pkg/utils/resourceid" + "github.com/go-chi/chi/v5" ua "github.com/mileusna/useragent" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" @@ -47,7 +47,7 @@ func init() { global.Register("appprovider", New) } -// Config holds the config options that need to be passed down to all ocdav handlers +// Config holds the config options for the HTTP appprovider service type Config struct { Prefix string `mapstructure:"prefix"` GatewaySvc string `mapstructure:"gatewaysvc"` @@ -62,7 +62,8 @@ func (c *Config) init() { } type svc struct { - conf *Config + conf *Config + router *chi.Mux } // New returns a new ocmd object @@ -74,12 +75,26 @@ func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) } conf.init() + r := chi.NewRouter() s := &svc{ - conf: conf, + conf: conf, + router: r, } + + if err := s.routerInit(); err != nil { + return nil, err + } + return s, nil } +func (s *svc) routerInit() error { + s.router.Get("/list", s.handleList) + s.router.Post("/new", s.handleNew) + s.router.Post("/open", s.handleOpen) + return nil +} + // Close performs cleanup. func (s *svc) Close() error { return nil @@ -95,29 +110,7 @@ func (s *svc) Unprotected() []string { func (s *svc) Handler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var head string - head, r.URL.Path = router.ShiftPath(r.URL.Path) - - switch r.Method { - case "POST": - switch head { - case "new": - s.handleNew(w, r) - case "open": - s.handleOpen(w, r) - default: - writeError(w, r, appErrorUnimplemented, "unsupported POST endpoint", nil) - } - case "GET": - switch head { - case "list": - s.handleList(w, r) - default: - writeError(w, r, appErrorUnimplemented, "unsupported GET endpoint", nil) - } - default: - writeError(w, r, appErrorUnimplemented, "unsupported method", nil) - } + s.router.ServeHTTP(w, r) }) } diff --git a/internal/http/services/loader/loader.go b/internal/http/services/loader/loader.go index 7548a93f32..6de231b478 100644 --- a/internal/http/services/loader/loader.go +++ b/internal/http/services/loader/loader.go @@ -31,6 +31,7 @@ import ( _ "github.com/cs3org/reva/internal/http/services/ocmd" _ "github.com/cs3org/reva/internal/http/services/owncloud/ocdav" _ "github.com/cs3org/reva/internal/http/services/owncloud/ocs" + _ "github.com/cs3org/reva/internal/http/services/preferences" _ "github.com/cs3org/reva/internal/http/services/prometheus" _ "github.com/cs3org/reva/internal/http/services/reverseproxy" _ "github.com/cs3org/reva/internal/http/services/siteacc" diff --git a/internal/http/services/preferences/preferences.go b/internal/http/services/preferences/preferences.go new file mode 100644 index 0000000000..f0127a160a --- /dev/null +++ b/internal/http/services/preferences/preferences.go @@ -0,0 +1,212 @@ +// 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 preferences + +import ( + "encoding/json" + "net/http" + + preferences "github.com/cs3org/go-cs3apis/cs3/preferences/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + "github.com/cs3org/reva/pkg/appctx" + "github.com/cs3org/reva/pkg/rgrpc/todo/pool" + "github.com/cs3org/reva/pkg/rhttp/global" + "github.com/cs3org/reva/pkg/sharedconf" + "github.com/go-chi/chi/v5" + "github.com/mitchellh/mapstructure" + "github.com/rs/zerolog" +) + +func init() { + global.Register("preferences", New) +} + +// Config holds the config options that for the preferences HTTP service +type Config struct { + Prefix string `mapstructure:"prefix"` + GatewaySvc string `mapstructure:"gatewaysvc"` +} + +func (c *Config) init() { + if c.Prefix == "" { + c.Prefix = "preferences" + } + c.GatewaySvc = sharedconf.GetGatewaySVC(c.GatewaySvc) +} + +type svc struct { + conf *Config + router *chi.Mux +} + +// New returns a new ocmd object +func New(m map[string]interface{}, log *zerolog.Logger) (global.Service, error) { + + conf := &Config{} + if err := mapstructure.Decode(m, conf); err != nil { + return nil, err + } + conf.init() + + r := chi.NewRouter() + s := &svc{ + conf: conf, + router: r, + } + + if err := s.routerInit(); err != nil { + return nil, err + } + + return s, nil +} + +func (s *svc) routerInit() error { + s.router.Get("/", s.handleGet) + s.router.Post("/", s.handlePost) + return nil +} + +// Close performs cleanup. +func (s *svc) Close() error { + return nil +} + +func (s *svc) Prefix() string { + return s.conf.Prefix +} + +func (s *svc) Unprotected() []string { + return []string{} +} + +func (s *svc) Handler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + s.router.ServeHTTP(w, r) + }) +} + +func (s *svc) handleGet(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := appctx.GetLogger(ctx) + + key := r.URL.Query().Get("key") + ns := r.URL.Query().Get("ns") + + if key == "" || ns == "" { + w.WriteHeader(http.StatusBadRequest) + if _, err := w.Write([]byte("key or namespace query missing")); err != nil { + log.Error().Err(err).Msg("error writing to response") + w.WriteHeader(http.StatusInternalServerError) + } + return + + } + + client, err := pool.GetGatewayServiceClient(s.conf.GatewaySvc) + if err != nil { + log.Error().Err(err).Msg("error getting grpc gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + res, err := client.GetKey(ctx, &preferences.GetKeyRequest{ + Key: &preferences.PreferenceKey{ + Namespace: ns, + Key: key, + }, + }) + if err != nil { + log.Error().Err(err).Msg("error retrieving key") + w.WriteHeader(http.StatusInternalServerError) + return + } + if res.Status.Code != rpc.Code_CODE_OK { + if res.Status.Code == rpc.Code_CODE_NOT_FOUND { + w.WriteHeader(http.StatusNotFound) + } else { + w.WriteHeader(http.StatusInternalServerError) + } + log.Error().Interface("status", res.Status).Msg("error retrieving key") + return + } + + js, err := json.Marshal(map[string]interface{}{ + "namespace": ns, + "key": key, + "value": res.Val, + }) + if err != nil { + log.Error().Err(err).Msg("error marshalling response") + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + if _, err = w.Write(js); err != nil { + log.Error().Err(err).Msg("error writing JSON response") + w.WriteHeader(http.StatusInternalServerError) + return + } + +} + +func (s *svc) handlePost(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := appctx.GetLogger(ctx) + + key := r.FormValue("key") + ns := r.FormValue("ns") + val := r.FormValue("value") + + if key == "" || ns == "" || val == "" { + w.WriteHeader(http.StatusBadRequest) + if _, err := w.Write([]byte("key, namespace or value parameter missing")); err != nil { + log.Error().Err(err).Msg("error writing to response") + w.WriteHeader(http.StatusInternalServerError) + } + return + + } + + client, err := pool.GetGatewayServiceClient(s.conf.GatewaySvc) + if err != nil { + log.Error().Err(err).Msg("error getting grpc gateway client") + w.WriteHeader(http.StatusInternalServerError) + return + } + + res, err := client.SetKey(ctx, &preferences.SetKeyRequest{ + Key: &preferences.PreferenceKey{ + Namespace: ns, + Key: key, + }, + Val: val, + }) + if err != nil { + log.Error().Err(err).Msg("error setting key") + w.WriteHeader(http.StatusInternalServerError) + return + } + if res.Status.Code != rpc.Code_CODE_OK { + w.WriteHeader(http.StatusInternalServerError) + log.Error().Interface("status", res.Status).Msg("error setting key") + return + } +} diff --git a/pkg/cbox/loader/loader.go b/pkg/cbox/loader/loader.go index 0dd9d07c4b..1e5baa9d0f 100644 --- a/pkg/cbox/loader/loader.go +++ b/pkg/cbox/loader/loader.go @@ -22,6 +22,7 @@ import ( // Load cbox specific drivers. _ "github.com/cs3org/reva/pkg/cbox/favorite/sql" _ "github.com/cs3org/reva/pkg/cbox/group/rest" + _ "github.com/cs3org/reva/pkg/cbox/preferences/sql" _ "github.com/cs3org/reva/pkg/cbox/publicshare/sql" _ "github.com/cs3org/reva/pkg/cbox/share/sql" _ "github.com/cs3org/reva/pkg/cbox/storage/eoshomewrapper" diff --git a/pkg/cbox/preferences/sql/sql.go b/pkg/cbox/preferences/sql/sql.go new file mode 100644 index 0000000000..67ba2ad731 --- /dev/null +++ b/pkg/cbox/preferences/sql/sql.go @@ -0,0 +1,100 @@ +// 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 sql + +import ( + "context" + "database/sql" + "fmt" + + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/preferences" + "github.com/cs3org/reva/pkg/preferences/registry" + "github.com/mitchellh/mapstructure" +) + +func init() { + registry.Register("sql", New) +} + +type config struct { + DbUsername string `mapstructure:"db_username"` + DbPassword string `mapstructure:"db_password"` + DbHost string `mapstructure:"db_host"` + DbPort int `mapstructure:"db_port"` + DbName string `mapstructure:"db_name"` +} + +type mgr struct { + c *config + db *sql.DB +} + +// New returns an instance of the cbox sql preferences manager. +func New(m map[string]interface{}) (preferences.Manager, error) { + c := &config{} + if err := mapstructure.Decode(m, c); err != nil { + return nil, err + } + + db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", c.DbUsername, c.DbPassword, c.DbHost, c.DbPort, c.DbName)) + if err != nil { + return nil, err + } + + return &mgr{ + c: c, + db: db, + }, nil +} + +func (m *mgr) SetKey(ctx context.Context, key, namespace, value string) error { + user, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + return errtypes.UserRequired("preferences: error getting user from ctx") + } + query := `INSERT INTO oc_preferences(userid, appid, configkey, configvalue) values(?, ?, ?, ?) ON DUPLICATE KEY UPDATE configvalue = ?` + params := []interface{}{user.Id.OpaqueId, namespace, key, value, value} + stmt, err := m.db.Prepare(query) + if err != nil { + return err + } + + if _, err = stmt.Exec(params...); err != nil { + return err + } + return nil +} + +func (m *mgr) GetKey(ctx context.Context, key, namespace string) (string, error) { + user, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + return "", errtypes.UserRequired("preferences: error getting user from ctx") + } + query := `SELECT configvalue FROM oc_preferences WHERE userid=? AND appid=? AND configkey=?` + var val string + if err := m.db.QueryRow(query, user.Id.OpaqueId, namespace, key).Scan(&val); err != nil { + if err == sql.ErrNoRows { + return "", errtypes.NotFound(namespace + ":" + key) + } + return "", err + } + return val, nil +} diff --git a/pkg/preferences/loader/loader.go b/pkg/preferences/loader/loader.go new file mode 100644 index 0000000000..a7b4d2c46c --- /dev/null +++ b/pkg/preferences/loader/loader.go @@ -0,0 +1,25 @@ +// 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 loader + +import ( + // Load preferences drivers. + _ "github.com/cs3org/reva/pkg/preferences/memory" + // Add your own here +) diff --git a/pkg/preferences/memory/memory.go b/pkg/preferences/memory/memory.go new file mode 100644 index 0000000000..b2c285e79d --- /dev/null +++ b/pkg/preferences/memory/memory.go @@ -0,0 +1,79 @@ +// 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 memory + +import ( + "context" + "sync" + + ctxpkg "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/errtypes" + "github.com/cs3org/reva/pkg/preferences" + "github.com/cs3org/reva/pkg/preferences/registry" +) + +func init() { + registry.Register("memory", New) +} + +type mgr struct { + sync.RWMutex + keys map[string]map[string]string +} + +// New returns an instance of the in-memory preferences manager. +func New(m map[string]interface{}) (preferences.Manager, error) { + return &mgr{keys: make(map[string]map[string]string)}, nil +} + +func (m *mgr) SetKey(ctx context.Context, key, namespace, value string) error { + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + return errtypes.UserRequired("preferences: error getting user from ctx") + } + m.Lock() + defer m.Unlock() + + userKey := u.Id.OpaqueId + + if len(m.keys[userKey]) == 0 { + m.keys[userKey] = map[string]string{key: value} + } else { + m.keys[userKey][key] = value + } + return nil +} + +func (m *mgr) GetKey(ctx context.Context, key, namespace string) (string, error) { + u, ok := ctxpkg.ContextGetUser(ctx) + if !ok { + return "", errtypes.UserRequired("preferences: error getting user from ctx") + } + m.RLock() + defer m.RUnlock() + + userKey := u.Id.OpaqueId + + if len(m.keys[userKey]) != 0 { + if value, ok := m.keys[userKey][key]; ok { + return value, nil + } + } + return "", errtypes.NotFound("preferences: key not found") +} diff --git a/pkg/preferences/preferences.go b/pkg/preferences/preferences.go new file mode 100644 index 0000000000..4f417396cb --- /dev/null +++ b/pkg/preferences/preferences.go @@ -0,0 +1,31 @@ +// 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 preferences + +import ( + "context" +) + +// Manager defines an interface for a preferences manager. +type Manager interface { + // SetKey sets a key under a specified namespace. + SetKey(ctx context.Context, key, namespace, value string) error + // GetKey returns the value for a combination of key and namespace, if set. + GetKey(ctx context.Context, key, namespace string) (string, error) +} diff --git a/pkg/preferences/registry/registry.go b/pkg/preferences/registry/registry.go new file mode 100644 index 0000000000..a8ca108f2d --- /dev/null +++ b/pkg/preferences/registry/registry.go @@ -0,0 +1,34 @@ +// 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 registry + +import "github.com/cs3org/reva/pkg/preferences" + +// NewFunc is the function that preferences implementations +// should register at init time. +type NewFunc func(map[string]interface{}) (preferences.Manager, error) + +// NewFuncs is a map containing all the registered preferences implementations. +var NewFuncs = map[string]NewFunc{} + +// Register registers a new preferences 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/storage/favorite/loader/loader.go b/pkg/storage/favorite/loader/loader.go index de90e7223c..7ebcb6cbd2 100644 --- a/pkg/storage/favorite/loader/loader.go +++ b/pkg/storage/favorite/loader/loader.go @@ -19,7 +19,7 @@ package loader import ( - // Load share cache drivers. + // Load storage favorite drivers. _ "github.com/cs3org/reva/pkg/storage/favorite/memory" // Add your own here )