Add 'settings/' from commit '230545a4a75b0611988fbcea51336a6c316d6a3d'

git-subtree-dir: settings
git-subtree-mainline: c26f7b390a
git-subtree-split: 230545a4a7
This commit is contained in:
A.Unger
2020-09-18 12:43:43 +02:00
140 changed files with 28757 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
package assets
import (
"net/http"
"os"
"path/filepath"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-settings/pkg/config"
// Fake the import to make the dep tree happy.
_ "golang.org/x/net/context"
// Fake the import to make the dep tree happy.
_ "golang.org/x/net/webdav"
)
//go:generate go run github.com/UnnoTed/fileb0x embed.yml
// assets gets initialized by New and provides the handler.
type assets struct {
logger log.Logger
config *config.Config
}
// Open just implements the HTTP filesystem interface.
func (a assets) Open(original string) (http.File, error) {
if a.config.Asset.Path != "" {
if stat, err := os.Stat(a.config.Asset.Path); err == nil && stat.IsDir() {
custom := filepath.Join(
a.config.Asset.Path,
original,
)
if _, err := os.Stat(custom); !os.IsNotExist(err) {
f, err := os.Open(custom)
if err != nil {
return nil, err
}
return f, nil
}
} else {
a.logger.Warn().
Str("path", a.config.Asset.Path).
Msg("Assets directory doesn't exist")
}
}
return FS.OpenFile(
CTX,
original,
os.O_RDONLY,
0644,
)
}
// New returns a new http filesystem to serve assets.
func New(opts ...Option) http.FileSystem {
options := newOptions(opts...)
return assets{
config: options.Config,
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,17 @@
---
pkg: "assets"
dest: "."
output: "embed.go"
fmt: true
noprefix: true
compression:
compress: true
custom:
- files:
- "../../assets/"
base: "../../assets/"
prefix: ""
...

View File

@@ -0,0 +1,40 @@
package assets
import (
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-settings/pkg/config"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Config *config.Config
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}

View File

@@ -0,0 +1,49 @@
package command
import (
"fmt"
"net/http"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis-settings/pkg/config"
"github.com/owncloud/ocis-settings/pkg/flagset"
)
// Health is the entrypoint for the health command.
func Health(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "health",
Usage: "Check health status",
Flags: flagset.HealthWithConfig(cfg),
Action: func(c *cli.Context) error {
logger := NewLogger(cfg)
resp, err := http.Get(
fmt.Sprintf(
"http://%s/healthz",
cfg.Debug.Addr,
),
)
if err != nil {
logger.Fatal().
Err(err).
Msg("Failed to request health check")
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
logger.Fatal().
Int("code", resp.StatusCode).
Msg("Health seems to be in bad state")
}
logger.Debug().
Int("code", resp.StatusCode).
Msg("Health got a good state")
return nil
},
}
}

View File

@@ -0,0 +1,108 @@
package command
import (
"os"
"strings"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-settings/pkg/config"
"github.com/owncloud/ocis-settings/pkg/flagset"
"github.com/owncloud/ocis-settings/pkg/version"
"github.com/spf13/viper"
)
// Execute is the entry point for the ocis-settings command.
func Execute() error {
cfg := config.New()
app := &cli.App{
Name: "ocis-settings",
Version: version.String,
Usage: "Provide settings and permissions for oCIS",
Compiled: version.Compiled(),
Authors: []*cli.Author{
{
Name: "ownCloud GmbH",
Email: "support@owncloud.com",
},
},
Flags: flagset.RootWithConfig(cfg),
Before: func(c *cli.Context) error {
return ParseConfig(c, cfg)
},
Commands: []*cli.Command{
Server(cfg),
Health(cfg),
},
}
cli.HelpFlag = &cli.BoolFlag{
Name: "help,h",
Usage: "Show the help",
}
cli.VersionFlag = &cli.BoolFlag{
Name: "version,v",
Usage: "Print the version",
}
return app.Run(os.Args)
}
// NewLogger initializes a service-specific logger instance.
func NewLogger(cfg *config.Config) log.Logger {
return log.NewLogger(
log.Name("settings"),
log.Level(cfg.Log.Level),
log.Pretty(cfg.Log.Pretty),
log.Color(cfg.Log.Color),
)
}
// ParseConfig loads settings configuration from Viper known paths.
func ParseConfig(c *cli.Context, cfg *config.Config) error {
logger := NewLogger(cfg)
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.SetEnvPrefix("SETTINGS")
viper.AutomaticEnv()
if c.IsSet("config-file") {
viper.SetConfigFile(c.String("config-file"))
} else {
viper.SetConfigName("settings")
viper.AddConfigPath("/etc/ocis")
viper.AddConfigPath("$HOME/.ocis")
viper.AddConfigPath("./config")
}
if err := viper.ReadInConfig(); err != nil {
switch err.(type) {
case viper.ConfigFileNotFoundError:
logger.Info().
Msg("Continue without config")
case viper.UnsupportedConfigError:
logger.Fatal().
Err(err).
Msg("Unsupported config type")
default:
logger.Fatal().
Err(err).
Msg("Failed to read config")
}
}
if err := viper.Unmarshal(&cfg); err != nil {
logger.Fatal().
Err(err).
Msg("Failed to parse config")
}
return nil
}

View File

@@ -0,0 +1,224 @@
package command
import (
"context"
"os"
"os/signal"
"strings"
"time"
"contrib.go.opencensus.io/exporter/jaeger"
"contrib.go.opencensus.io/exporter/ocagent"
"contrib.go.opencensus.io/exporter/zipkin"
"github.com/micro/cli/v2"
"github.com/oklog/run"
openzipkin "github.com/openzipkin/zipkin-go"
zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http"
"github.com/owncloud/ocis-settings/pkg/config"
"github.com/owncloud/ocis-settings/pkg/flagset"
"github.com/owncloud/ocis-settings/pkg/server/debug"
"github.com/owncloud/ocis-settings/pkg/server/grpc"
"github.com/owncloud/ocis-settings/pkg/server/http"
"go.opencensus.io/stats/view"
"go.opencensus.io/trace"
)
// Server is the entrypoint for the server command.
func Server(cfg *config.Config) *cli.Command {
return &cli.Command{
Name: "server",
Usage: "Start integrated server",
Flags: flagset.ServerWithConfig(cfg),
Before: func(ctx *cli.Context) error {
if cfg.HTTP.Root != "/" {
cfg.HTTP.Root = strings.TrimSuffix(cfg.HTTP.Root, "/")
}
// When running on single binary mode the before hook from the root command won't get called. We manually
// call this before hook from ocis command, so the configuration can be loaded.
return ParseConfig(ctx, cfg)
},
Action: func(c *cli.Context) error {
logger := NewLogger(cfg)
if cfg.Tracing.Enabled {
switch t := cfg.Tracing.Type; t {
case "agent":
exporter, err := ocagent.NewExporter(
ocagent.WithReconnectionPeriod(5*time.Second),
ocagent.WithAddress(cfg.Tracing.Endpoint),
ocagent.WithServiceName(cfg.Tracing.Service),
)
if err != nil {
logger.Error().
Err(err).
Str("endpoint", cfg.Tracing.Endpoint).
Str("collector", cfg.Tracing.Collector).
Msg("Failed to create agent tracing")
return err
}
trace.RegisterExporter(exporter)
view.RegisterExporter(exporter)
case "jaeger":
exporter, err := jaeger.NewExporter(
jaeger.Options{
AgentEndpoint: cfg.Tracing.Endpoint,
CollectorEndpoint: cfg.Tracing.Collector,
ServiceName: cfg.Tracing.Service,
},
)
if err != nil {
logger.Error().
Err(err).
Str("endpoint", cfg.Tracing.Endpoint).
Str("collector", cfg.Tracing.Collector).
Msg("Failed to create jaeger tracing")
return err
}
trace.RegisterExporter(exporter)
case "zipkin":
endpoint, err := openzipkin.NewEndpoint(
cfg.Tracing.Service,
cfg.Tracing.Endpoint,
)
if err != nil {
logger.Error().
Err(err).
Str("endpoint", cfg.Tracing.Endpoint).
Str("collector", cfg.Tracing.Collector).
Msg("Failed to create zipkin tracing")
return err
}
exporter := zipkin.NewExporter(
zipkinhttp.NewReporter(
cfg.Tracing.Collector,
),
endpoint,
)
trace.RegisterExporter(exporter)
default:
logger.Warn().
Str("type", t).
Msg("Unknown tracing backend")
}
trace.ApplyConfig(
trace.Config{
DefaultSampler: trace.AlwaysSample(),
},
)
} else {
logger.Debug().
Msg("Tracing is not enabled")
}
var (
gr = run.Group{}
ctx, cancel = context.WithCancel(context.Background())
)
defer cancel()
{
server := http.Server(
http.Name("settings"),
http.Logger(logger),
http.Context(ctx),
http.Config(cfg),
http.Flags(flagset.RootWithConfig(cfg)),
http.Flags(flagset.ServerWithConfig(cfg)),
)
gr.Add(server.Run, func(_ error) {
logger.Info().
Str("server", "http").
Msg("Shutting down server")
cancel()
})
}
{
server := grpc.Server(
grpc.Name("settings"),
grpc.Logger(logger),
grpc.Context(ctx),
grpc.Config(cfg),
)
gr.Add(server.Run, func(_ error) {
logger.Info().
Str("server", "grpc").
Msg("Shutting down server")
cancel()
})
}
{
server, err := debug.Server(
debug.Logger(logger),
debug.Context(ctx),
debug.Config(cfg),
)
if err != nil {
logger.Error().
Err(err).
Str("server", "debug").
Msg("Failed to initialize server")
return err
}
gr.Add(server.ListenAndServe, func(_ error) {
ctx, timeout := context.WithTimeout(ctx, 5*time.Second)
defer timeout()
defer cancel()
if err := server.Shutdown(ctx); err != nil {
logger.Error().
Err(err).
Str("server", "debug").
Msg("Failed to shutdown server")
} else {
logger.Info().
Str("server", "debug").
Msg("Shutting down server")
}
})
}
{
stop := make(chan os.Signal, 1)
gr.Add(func() error {
signal.Notify(stop, os.Interrupt)
<-stop
return nil
}, func(err error) {
close(stop)
cancel()
})
}
return gr.Run()
},
}
}

View File

@@ -0,0 +1,71 @@
package config
// Log defines the available logging configuration.
type Log struct {
Level string
Pretty bool
Color bool
}
// Debug defines the available debug configuration.
type Debug struct {
Addr string
Token string
Pprof bool
Zpages bool
}
// HTTP defines the available http configuration.
type HTTP struct {
Addr string
Namespace string
Root string
}
// GRPC defines the available grpc configuration.
type GRPC struct {
Addr string
Namespace string
}
// Tracing defines the available tracing configuration.
type Tracing struct {
Enabled bool
Type string
Endpoint string
Collector string
Service string
}
// Asset undocumented
type Asset struct {
Path string
}
// Storage defines the available storage configuration.
type Storage struct {
DataPath string
}
// TokenManager is the config for using the reva token manager
type TokenManager struct {
JWTSecret string
}
// Config combines all available configuration parts.
type Config struct {
File string
Storage Storage
Log Log
Debug Debug
HTTP HTTP
GRPC GRPC
Tracing Tracing
Asset Asset
TokenManager TokenManager
}
// New initializes a new configuration with or without defaults.
func New() *Config {
return &Config{}
}

View File

@@ -0,0 +1,175 @@
package flagset
import (
"github.com/micro/cli/v2"
"github.com/owncloud/ocis-settings/pkg/config"
)
// RootWithConfig applies cfg to the root flagset
func RootWithConfig(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "config-file",
Value: "",
Usage: "Path to config file",
EnvVars: []string{"SETTINGS_CONFIG_FILE"},
Destination: &cfg.File,
},
&cli.StringFlag{
Name: "log-level",
Value: "info",
Usage: "Set logging level",
EnvVars: []string{"SETTINGS_LOG_LEVEL"},
Destination: &cfg.Log.Level,
},
&cli.BoolFlag{
Name: "log-pretty",
Value: true,
Usage: "Enable pretty logging",
EnvVars: []string{"SETTINGS_LOG_PRETTY"},
Destination: &cfg.Log.Pretty,
},
&cli.BoolFlag{
Name: "log-color",
Value: true,
Usage: "Enable colored logging",
EnvVars: []string{"SETTINGS_LOG_COLOR"},
Destination: &cfg.Log.Color,
},
}
}
// HealthWithConfig applies cfg to the root flagset
func HealthWithConfig(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "debug-addr",
Value: "0.0.0.0:9194",
Usage: "Address to debug endpoint",
EnvVars: []string{"SETTINGS_DEBUG_ADDR"},
Destination: &cfg.Debug.Addr,
},
}
}
// ServerWithConfig applies cfg to the root flagset
func ServerWithConfig(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.BoolFlag{
Name: "tracing-enabled",
Usage: "Enable sending traces",
EnvVars: []string{"SETTINGS_TRACING_ENABLED"},
Destination: &cfg.Tracing.Enabled,
},
&cli.StringFlag{
Name: "tracing-type",
Value: "jaeger",
Usage: "Tracing backend type",
EnvVars: []string{"SETTINGS_TRACING_TYPE"},
Destination: &cfg.Tracing.Type,
},
&cli.StringFlag{
Name: "tracing-endpoint",
Value: "",
Usage: "Endpoint for the agent",
EnvVars: []string{"SETTINGS_TRACING_ENDPOINT"},
Destination: &cfg.Tracing.Endpoint,
},
&cli.StringFlag{
Name: "tracing-collector",
Value: "",
Usage: "Endpoint for the collector",
EnvVars: []string{"SETTINGS_TRACING_COLLECTOR"},
Destination: &cfg.Tracing.Collector,
},
&cli.StringFlag{
Name: "tracing-service",
Value: "settings",
Usage: "Service name for tracing",
EnvVars: []string{"SETTINGS_TRACING_SERVICE"},
Destination: &cfg.Tracing.Service,
},
&cli.StringFlag{
Name: "debug-addr",
Value: "0.0.0.0:9194",
Usage: "Address to bind debug server",
EnvVars: []string{"SETTINGS_DEBUG_ADDR"},
Destination: &cfg.Debug.Addr,
},
&cli.StringFlag{
Name: "debug-token",
Value: "",
Usage: "Token to grant metrics access",
EnvVars: []string{"SETTINGS_DEBUG_TOKEN"},
Destination: &cfg.Debug.Token,
},
&cli.BoolFlag{
Name: "debug-pprof",
Usage: "Enable pprof debugging",
EnvVars: []string{"SETTINGS_DEBUG_PPROF"},
Destination: &cfg.Debug.Pprof,
},
&cli.BoolFlag{
Name: "debug-zpages",
Usage: "Enable zpages debugging",
EnvVars: []string{"SETTINGS_DEBUG_ZPAGES"},
Destination: &cfg.Debug.Zpages,
},
&cli.StringFlag{
Name: "http-addr",
Value: "0.0.0.0:9190",
Usage: "Address to bind http server",
EnvVars: []string{"SETTINGS_HTTP_ADDR"},
Destination: &cfg.HTTP.Addr,
},
&cli.StringFlag{
Name: "http-namespace",
Value: "com.owncloud.web",
Usage: "Set the base namespace for the http namespace",
EnvVars: []string{"SETTINGS_HTTP_NAMESPACE"},
Destination: &cfg.HTTP.Namespace,
},
&cli.StringFlag{
Name: "http-root",
Value: "/",
Usage: "Root path of http server",
EnvVars: []string{"SETTINGS_HTTP_ROOT"},
Destination: &cfg.HTTP.Root,
},
&cli.StringFlag{
Name: "grpc-addr",
Value: "0.0.0.0:9191",
Usage: "Address to bind grpc server",
EnvVars: []string{"SETTINGS_GRPC_ADDR"},
Destination: &cfg.GRPC.Addr,
},
&cli.StringFlag{
Name: "asset-path",
Value: "",
Usage: "Path to custom assets",
EnvVars: []string{"SETTINGS_ASSET_PATH"},
Destination: &cfg.Asset.Path,
},
&cli.StringFlag{
Name: "grpc-namespace",
Value: "com.owncloud.api",
Usage: "Set the base namespace for the grpc namespace",
EnvVars: []string{"SETTINGS_GRPC_NAMESPACE"},
Destination: &cfg.GRPC.Namespace,
},
&cli.StringFlag{
Name: "data-path",
Value: "/var/tmp/ocis-settings",
Usage: "Mount path for the storage",
EnvVars: []string{"SETTINGS_DATA_PATH"},
Destination: &cfg.Storage.DataPath,
},
&cli.StringFlag{
Name: "jwt-secret",
Value: "Pive-Fumkiu4",
Usage: "Used to create JWT to talk to reva, should equal reva's jwt-secret",
EnvVars: []string{"SETTINGS_JWT_SECRET"},
Destination: &cfg.TokenManager.JWTSecret,
},
}
}

View File

@@ -0,0 +1,32 @@
package metrics
var (
// Namespace defines the namespace for the defines metrics.
Namespace = "ocis"
// Subsystem defines the subsystem for the defines metrics.
Subsystem = "settings"
)
// Metrics defines the available metrics of this service.
type Metrics struct {
// Counter *prometheus.CounterVec
}
// New initializes the available metrics.
func New() *Metrics {
m := &Metrics{
// Counter: prometheus.NewCounterVec(prometheus.CounterOpts{
// Namespace: Namespace,
// Subsystem: Subsystem,
// Name: "greet_total",
// Help: "How many greeting requests processed",
// }, []string{}),
}
// prometheus.Register(
// m.Counter,
// )
return m
}

View File

@@ -0,0 +1,186 @@
package proto
import (
"context"
"github.com/golang/protobuf/ptypes/empty"
"github.com/micro/go-micro/v2/client"
)
// MockBundleService can be used to write tests against the bundle service.
/*
To create a mock overwrite the functions of an instance like this:
```go
func mockBundleSvc(returnErr bool) proto.BundleService {
if returnErr {
return &proto.MockBundleService{
ListBundlesFunc: func(ctx context.Context, in *proto.ListBundlesRequest, opts ...client.CallOption) (out *proto.ListBundlesResponse, err error) {
return nil, fmt.Errorf("error returned by mockBundleSvc LIST")
},
}
}
return &proto.MockBundleService{
ListBundlesFunc: func(ctx context.Context, in *proto.ListBundlesRequest, opts ...client.CallOption) (out *proto.ListBundlesResponse, err error) {
return &proto.ListBundlesResponse{
Bundles: []*proto.Bundle{
{
Id: "hello-there",
},
},
}, nil
},
}
}
```
*/
type MockBundleService struct {
ListBundlesFunc func(ctx context.Context, req *ListBundlesRequest, opts ...client.CallOption) (*ListBundlesResponse, error)
GetBundleFunc func(ctx context.Context, req *GetBundleRequest, opts ...client.CallOption) (*GetBundleResponse, error)
SaveBundleFunc func(ctx context.Context, req *SaveBundleRequest, opts ...client.CallOption) (*SaveBundleResponse, error)
AddSettingToBundleFunc func(ctx context.Context, req *AddSettingToBundleRequest, opts ...client.CallOption) (*AddSettingToBundleResponse, error)
RemoveSettingFromBundleFunc func(ctx context.Context, req *RemoveSettingFromBundleRequest, opts ...client.CallOption) (*empty.Empty, error)
}
// ListBundles will panic if the function has been called, but not mocked
func (m MockBundleService) ListBundles(ctx context.Context, req *ListBundlesRequest, opts ...client.CallOption) (*ListBundlesResponse, error) {
if m.ListBundlesFunc != nil {
return m.ListBundlesFunc(ctx, req, opts...)
}
panic("ListBundlesFunc was called in test but not mocked")
}
// GetBundle will panic if the function has been called, but not mocked
func (m MockBundleService) GetBundle(ctx context.Context, req *GetBundleRequest, opts ...client.CallOption) (*GetBundleResponse, error) {
if m.GetBundleFunc != nil {
return m.GetBundleFunc(ctx, req, opts...)
}
panic("GetBundleFunc was called in test but not mocked")
}
// SaveBundle will panic if the function has been called, but not mocked
func (m MockBundleService) SaveBundle(ctx context.Context, req *SaveBundleRequest, opts ...client.CallOption) (*SaveBundleResponse, error) {
if m.SaveBundleFunc != nil {
return m.SaveBundleFunc(ctx, req, opts...)
}
panic("SaveBundleFunc was called in test but not mocked")
}
// AddSettingToBundle will panic if the function has been called, but not mocked
func (m MockBundleService) AddSettingToBundle(ctx context.Context, req *AddSettingToBundleRequest, opts ...client.CallOption) (*AddSettingToBundleResponse, error) {
if m.AddSettingToBundleFunc != nil {
return m.AddSettingToBundleFunc(ctx, req, opts...)
}
panic("AddSettingToBundleFunc was called in test but not mocked")
}
// RemoveSettingFromBundle will panic if the function has been called, but not mocked
func (m MockBundleService) RemoveSettingFromBundle(ctx context.Context, req *RemoveSettingFromBundleRequest, opts ...client.CallOption) (*empty.Empty, error) {
if m.RemoveSettingFromBundleFunc != nil {
return m.RemoveSettingFromBundleFunc(ctx, req, opts...)
}
panic("RemoveSettingFromBundleFunc was called in test but not mocked")
}
// MockValueService can be used to write tests against the value service.
type MockValueService struct {
ListValuesFunc func(ctx context.Context, req *ListValuesRequest, opts ...client.CallOption) (*ListValuesResponse, error)
GetValueFunc func(ctx context.Context, req *GetValueRequest, opts ...client.CallOption) (*GetValueResponse, error)
GetValueByUniqueIdentifiersFunc func(ctx context.Context, req *GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*GetValueResponse, error)
SaveValueFunc func(ctx context.Context, req *SaveValueRequest, opts ...client.CallOption) (*SaveValueResponse, error)
}
// ListValues will panic if the function has been called, but not mocked
func (m MockValueService) ListValues(ctx context.Context, req *ListValuesRequest, opts ...client.CallOption) (*ListValuesResponse, error) {
if m.ListValuesFunc != nil {
return m.ListValuesFunc(ctx, req, opts...)
}
panic("ListValuesFunc was called in test but not mocked")
}
// GetValue will panic if the function has been called, but not mocked
func (m MockValueService) GetValue(ctx context.Context, req *GetValueRequest, opts ...client.CallOption) (*GetValueResponse, error) {
if m.GetValueFunc != nil {
return m.GetValueFunc(ctx, req, opts...)
}
panic("GetValueFunc was called in test but not mocked")
}
// GetValueByUniqueIdentifiers will panic if the function has been called, but not mocked
func (m MockValueService) GetValueByUniqueIdentifiers(ctx context.Context, req *GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*GetValueResponse, error) {
if m.GetValueByUniqueIdentifiersFunc != nil {
return m.GetValueByUniqueIdentifiersFunc(ctx, req, opts...)
}
panic("GetValueByUniqueIdentifiersFunc was called in test but not mocked")
}
// SaveValue will panic if the function has been called, but not mocked
func (m MockValueService) SaveValue(ctx context.Context, req *SaveValueRequest, opts ...client.CallOption) (*SaveValueResponse, error) {
if m.SaveValueFunc != nil {
return m.SaveValueFunc(ctx, req, opts...)
}
panic("SaveValueFunc was called in test but not mocked")
}
// MockRoleService will panic if the function has been called, but not mocked
type MockRoleService struct {
ListRolesFunc func(ctx context.Context, req *ListBundlesRequest, opts ...client.CallOption) (*ListBundlesResponse, error)
ListRoleAssignmentsFunc func(ctx context.Context, req *ListRoleAssignmentsRequest, opts ...client.CallOption) (*ListRoleAssignmentsResponse, error)
AssignRoleToUserFunc func(ctx context.Context, req *AssignRoleToUserRequest, opts ...client.CallOption) (*AssignRoleToUserResponse, error)
RemoveRoleFromUserFunc func(ctx context.Context, req *RemoveRoleFromUserRequest, opts ...client.CallOption) (*empty.Empty, error)
}
// ListRoles will panic if the function has been called, but not mocked
func (m MockRoleService) ListRoles(ctx context.Context, req *ListBundlesRequest, opts ...client.CallOption) (*ListBundlesResponse, error) {
if m.ListRolesFunc != nil {
return m.ListRolesFunc(ctx, req, opts...)
}
panic("ListRolesFunc was called in test but not mocked")
}
// ListRoleAssignments will panic if the function has been called, but not mocked
func (m MockRoleService) ListRoleAssignments(ctx context.Context, req *ListRoleAssignmentsRequest, opts ...client.CallOption) (*ListRoleAssignmentsResponse, error) {
if m.ListRoleAssignmentsFunc != nil {
return m.ListRoleAssignmentsFunc(ctx, req, opts...)
}
panic("ListRoleAssignmentsFunc was called in test but not mocked")
}
// AssignRoleToUser will panic if the function has been called, but not mocked
func (m MockRoleService) AssignRoleToUser(ctx context.Context, req *AssignRoleToUserRequest, opts ...client.CallOption) (*AssignRoleToUserResponse, error) {
if m.AssignRoleToUserFunc != nil {
return m.AssignRoleToUserFunc(ctx, req, opts...)
}
panic("AssignRoleToUserFunc was called in test but not mocked")
}
// RemoveRoleFromUser will panic if the function has been called, but not mocked
func (m MockRoleService) RemoveRoleFromUser(ctx context.Context, req *RemoveRoleFromUserRequest, opts ...client.CallOption) (*empty.Empty, error) {
if m.RemoveRoleFromUserFunc != nil {
return m.RemoveRoleFromUserFunc(ctx, req, opts...)
}
panic("RemoveRoleFromUserFunc was called in test but not mocked")
}
// MockPermissionService will panic if the function has been called, but not mocked
type MockPermissionService struct {
ListPermissionsByResourceFunc func(ctx context.Context, req *ListPermissionsByResourceRequest, opts ...client.CallOption) (*ListPermissionsByResourceResponse, error)
GetPermissionByIDFunc func(ctx context.Context, req *GetPermissionByIDRequest, opts ...client.CallOption) (*GetPermissionByIDResponse, error)
}
// ListPermissionsByResource will panic if the function has been called, but not mocked
func (m MockPermissionService) ListPermissionsByResource(ctx context.Context, req *ListPermissionsByResourceRequest, opts ...client.CallOption) (*ListPermissionsByResourceResponse, error) {
if m.ListPermissionsByResourceFunc != nil {
return m.ListPermissionsByResourceFunc(ctx, req, opts...)
}
panic("ListPermissionsByResourceFunc was called in test but not mocked")
}
// GetPermissionByID will panic if the function has been called, but not mocked
func (m MockPermissionService) GetPermissionByID(ctx context.Context, req *GetPermissionByIDRequest, opts ...client.CallOption) (*GetPermissionByIDResponse, error) {
if m.GetPermissionByIDFunc != nil {
return m.GetPermissionByIDFunc(ctx, req, opts...)
}
panic("GetPermissionByIDFunc was called in test but not mocked")
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,674 @@
// Code generated by protoc-gen-micro. DO NOT EDIT.
// source: settings.proto
package proto
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
empty "github.com/golang/protobuf/ptypes/empty"
_ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger/options"
_ "google.golang.org/genproto/googleapis/api/annotations"
math "math"
)
import (
context "context"
api "github.com/micro/go-micro/v2/api"
client "github.com/micro/go-micro/v2/client"
server "github.com/micro/go-micro/v2/server"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// Reference imports to suppress errors if they are not otherwise used.
var _ api.Endpoint
var _ context.Context
var _ client.Option
var _ server.Option
// Api Endpoints for BundleService service
func NewBundleServiceEndpoints() []*api.Endpoint {
return []*api.Endpoint{
&api.Endpoint{
Name: "BundleService.SaveBundle",
Path: []string{"/api/v0/settings/bundle-save"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
&api.Endpoint{
Name: "BundleService.GetBundle",
Path: []string{"/api/v0/settings/bundle-get"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
&api.Endpoint{
Name: "BundleService.ListBundles",
Path: []string{"/api/v0/settings/bundles-list"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
&api.Endpoint{
Name: "BundleService.AddSettingToBundle",
Path: []string{"/api/v0/settings/bundles-add-setting"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
&api.Endpoint{
Name: "BundleService.RemoveSettingFromBundle",
Path: []string{"/api/v0/settings/bundles-remove-setting"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
}
}
// Client API for BundleService service
type BundleService interface {
SaveBundle(ctx context.Context, in *SaveBundleRequest, opts ...client.CallOption) (*SaveBundleResponse, error)
GetBundle(ctx context.Context, in *GetBundleRequest, opts ...client.CallOption) (*GetBundleResponse, error)
ListBundles(ctx context.Context, in *ListBundlesRequest, opts ...client.CallOption) (*ListBundlesResponse, error)
AddSettingToBundle(ctx context.Context, in *AddSettingToBundleRequest, opts ...client.CallOption) (*AddSettingToBundleResponse, error)
RemoveSettingFromBundle(ctx context.Context, in *RemoveSettingFromBundleRequest, opts ...client.CallOption) (*empty.Empty, error)
}
type bundleService struct {
c client.Client
name string
}
func NewBundleService(name string, c client.Client) BundleService {
return &bundleService{
c: c,
name: name,
}
}
func (c *bundleService) SaveBundle(ctx context.Context, in *SaveBundleRequest, opts ...client.CallOption) (*SaveBundleResponse, error) {
req := c.c.NewRequest(c.name, "BundleService.SaveBundle", in)
out := new(SaveBundleResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bundleService) GetBundle(ctx context.Context, in *GetBundleRequest, opts ...client.CallOption) (*GetBundleResponse, error) {
req := c.c.NewRequest(c.name, "BundleService.GetBundle", in)
out := new(GetBundleResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bundleService) ListBundles(ctx context.Context, in *ListBundlesRequest, opts ...client.CallOption) (*ListBundlesResponse, error) {
req := c.c.NewRequest(c.name, "BundleService.ListBundles", in)
out := new(ListBundlesResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bundleService) AddSettingToBundle(ctx context.Context, in *AddSettingToBundleRequest, opts ...client.CallOption) (*AddSettingToBundleResponse, error) {
req := c.c.NewRequest(c.name, "BundleService.AddSettingToBundle", in)
out := new(AddSettingToBundleResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *bundleService) RemoveSettingFromBundle(ctx context.Context, in *RemoveSettingFromBundleRequest, opts ...client.CallOption) (*empty.Empty, error) {
req := c.c.NewRequest(c.name, "BundleService.RemoveSettingFromBundle", in)
out := new(empty.Empty)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for BundleService service
type BundleServiceHandler interface {
SaveBundle(context.Context, *SaveBundleRequest, *SaveBundleResponse) error
GetBundle(context.Context, *GetBundleRequest, *GetBundleResponse) error
ListBundles(context.Context, *ListBundlesRequest, *ListBundlesResponse) error
AddSettingToBundle(context.Context, *AddSettingToBundleRequest, *AddSettingToBundleResponse) error
RemoveSettingFromBundle(context.Context, *RemoveSettingFromBundleRequest, *empty.Empty) error
}
func RegisterBundleServiceHandler(s server.Server, hdlr BundleServiceHandler, opts ...server.HandlerOption) error {
type bundleService interface {
SaveBundle(ctx context.Context, in *SaveBundleRequest, out *SaveBundleResponse) error
GetBundle(ctx context.Context, in *GetBundleRequest, out *GetBundleResponse) error
ListBundles(ctx context.Context, in *ListBundlesRequest, out *ListBundlesResponse) error
AddSettingToBundle(ctx context.Context, in *AddSettingToBundleRequest, out *AddSettingToBundleResponse) error
RemoveSettingFromBundle(ctx context.Context, in *RemoveSettingFromBundleRequest, out *empty.Empty) error
}
type BundleService struct {
bundleService
}
h := &bundleServiceHandler{hdlr}
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "BundleService.SaveBundle",
Path: []string{"/api/v0/settings/bundle-save"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "BundleService.GetBundle",
Path: []string{"/api/v0/settings/bundle-get"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "BundleService.ListBundles",
Path: []string{"/api/v0/settings/bundles-list"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "BundleService.AddSettingToBundle",
Path: []string{"/api/v0/settings/bundles-add-setting"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "BundleService.RemoveSettingFromBundle",
Path: []string{"/api/v0/settings/bundles-remove-setting"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
return s.Handle(s.NewHandler(&BundleService{h}, opts...))
}
type bundleServiceHandler struct {
BundleServiceHandler
}
func (h *bundleServiceHandler) SaveBundle(ctx context.Context, in *SaveBundleRequest, out *SaveBundleResponse) error {
return h.BundleServiceHandler.SaveBundle(ctx, in, out)
}
func (h *bundleServiceHandler) GetBundle(ctx context.Context, in *GetBundleRequest, out *GetBundleResponse) error {
return h.BundleServiceHandler.GetBundle(ctx, in, out)
}
func (h *bundleServiceHandler) ListBundles(ctx context.Context, in *ListBundlesRequest, out *ListBundlesResponse) error {
return h.BundleServiceHandler.ListBundles(ctx, in, out)
}
func (h *bundleServiceHandler) AddSettingToBundle(ctx context.Context, in *AddSettingToBundleRequest, out *AddSettingToBundleResponse) error {
return h.BundleServiceHandler.AddSettingToBundle(ctx, in, out)
}
func (h *bundleServiceHandler) RemoveSettingFromBundle(ctx context.Context, in *RemoveSettingFromBundleRequest, out *empty.Empty) error {
return h.BundleServiceHandler.RemoveSettingFromBundle(ctx, in, out)
}
// Api Endpoints for ValueService service
func NewValueServiceEndpoints() []*api.Endpoint {
return []*api.Endpoint{
&api.Endpoint{
Name: "ValueService.SaveValue",
Path: []string{"/api/v0/settings/values-save"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
&api.Endpoint{
Name: "ValueService.GetValue",
Path: []string{"/api/v0/settings/values-get"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
&api.Endpoint{
Name: "ValueService.ListValues",
Path: []string{"/api/v0/settings/values-list"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
&api.Endpoint{
Name: "ValueService.GetValueByUniqueIdentifiers",
Path: []string{"/api/v0/settings/values-get-by-unique-identifiers"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
}
}
// Client API for ValueService service
type ValueService interface {
SaveValue(ctx context.Context, in *SaveValueRequest, opts ...client.CallOption) (*SaveValueResponse, error)
GetValue(ctx context.Context, in *GetValueRequest, opts ...client.CallOption) (*GetValueResponse, error)
ListValues(ctx context.Context, in *ListValuesRequest, opts ...client.CallOption) (*ListValuesResponse, error)
GetValueByUniqueIdentifiers(ctx context.Context, in *GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*GetValueResponse, error)
}
type valueService struct {
c client.Client
name string
}
func NewValueService(name string, c client.Client) ValueService {
return &valueService{
c: c,
name: name,
}
}
func (c *valueService) SaveValue(ctx context.Context, in *SaveValueRequest, opts ...client.CallOption) (*SaveValueResponse, error) {
req := c.c.NewRequest(c.name, "ValueService.SaveValue", in)
out := new(SaveValueResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *valueService) GetValue(ctx context.Context, in *GetValueRequest, opts ...client.CallOption) (*GetValueResponse, error) {
req := c.c.NewRequest(c.name, "ValueService.GetValue", in)
out := new(GetValueResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *valueService) ListValues(ctx context.Context, in *ListValuesRequest, opts ...client.CallOption) (*ListValuesResponse, error) {
req := c.c.NewRequest(c.name, "ValueService.ListValues", in)
out := new(ListValuesResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *valueService) GetValueByUniqueIdentifiers(ctx context.Context, in *GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*GetValueResponse, error) {
req := c.c.NewRequest(c.name, "ValueService.GetValueByUniqueIdentifiers", in)
out := new(GetValueResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for ValueService service
type ValueServiceHandler interface {
SaveValue(context.Context, *SaveValueRequest, *SaveValueResponse) error
GetValue(context.Context, *GetValueRequest, *GetValueResponse) error
ListValues(context.Context, *ListValuesRequest, *ListValuesResponse) error
GetValueByUniqueIdentifiers(context.Context, *GetValueByUniqueIdentifiersRequest, *GetValueResponse) error
}
func RegisterValueServiceHandler(s server.Server, hdlr ValueServiceHandler, opts ...server.HandlerOption) error {
type valueService interface {
SaveValue(ctx context.Context, in *SaveValueRequest, out *SaveValueResponse) error
GetValue(ctx context.Context, in *GetValueRequest, out *GetValueResponse) error
ListValues(ctx context.Context, in *ListValuesRequest, out *ListValuesResponse) error
GetValueByUniqueIdentifiers(ctx context.Context, in *GetValueByUniqueIdentifiersRequest, out *GetValueResponse) error
}
type ValueService struct {
valueService
}
h := &valueServiceHandler{hdlr}
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "ValueService.SaveValue",
Path: []string{"/api/v0/settings/values-save"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "ValueService.GetValue",
Path: []string{"/api/v0/settings/values-get"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "ValueService.ListValues",
Path: []string{"/api/v0/settings/values-list"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "ValueService.GetValueByUniqueIdentifiers",
Path: []string{"/api/v0/settings/values-get-by-unique-identifiers"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
return s.Handle(s.NewHandler(&ValueService{h}, opts...))
}
type valueServiceHandler struct {
ValueServiceHandler
}
func (h *valueServiceHandler) SaveValue(ctx context.Context, in *SaveValueRequest, out *SaveValueResponse) error {
return h.ValueServiceHandler.SaveValue(ctx, in, out)
}
func (h *valueServiceHandler) GetValue(ctx context.Context, in *GetValueRequest, out *GetValueResponse) error {
return h.ValueServiceHandler.GetValue(ctx, in, out)
}
func (h *valueServiceHandler) ListValues(ctx context.Context, in *ListValuesRequest, out *ListValuesResponse) error {
return h.ValueServiceHandler.ListValues(ctx, in, out)
}
func (h *valueServiceHandler) GetValueByUniqueIdentifiers(ctx context.Context, in *GetValueByUniqueIdentifiersRequest, out *GetValueResponse) error {
return h.ValueServiceHandler.GetValueByUniqueIdentifiers(ctx, in, out)
}
// Api Endpoints for RoleService service
func NewRoleServiceEndpoints() []*api.Endpoint {
return []*api.Endpoint{
&api.Endpoint{
Name: "RoleService.ListRoles",
Path: []string{"/api/v0/settings/roles-list"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
&api.Endpoint{
Name: "RoleService.ListRoleAssignments",
Path: []string{"/api/v0/settings/assignments-list"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
&api.Endpoint{
Name: "RoleService.AssignRoleToUser",
Path: []string{"/api/v0/settings/assignments-add"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
&api.Endpoint{
Name: "RoleService.RemoveRoleFromUser",
Path: []string{"/api/v0/settings/assignments-remove"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
}
}
// Client API for RoleService service
type RoleService interface {
ListRoles(ctx context.Context, in *ListBundlesRequest, opts ...client.CallOption) (*ListBundlesResponse, error)
ListRoleAssignments(ctx context.Context, in *ListRoleAssignmentsRequest, opts ...client.CallOption) (*ListRoleAssignmentsResponse, error)
AssignRoleToUser(ctx context.Context, in *AssignRoleToUserRequest, opts ...client.CallOption) (*AssignRoleToUserResponse, error)
RemoveRoleFromUser(ctx context.Context, in *RemoveRoleFromUserRequest, opts ...client.CallOption) (*empty.Empty, error)
}
type roleService struct {
c client.Client
name string
}
func NewRoleService(name string, c client.Client) RoleService {
return &roleService{
c: c,
name: name,
}
}
func (c *roleService) ListRoles(ctx context.Context, in *ListBundlesRequest, opts ...client.CallOption) (*ListBundlesResponse, error) {
req := c.c.NewRequest(c.name, "RoleService.ListRoles", in)
out := new(ListBundlesResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *roleService) ListRoleAssignments(ctx context.Context, in *ListRoleAssignmentsRequest, opts ...client.CallOption) (*ListRoleAssignmentsResponse, error) {
req := c.c.NewRequest(c.name, "RoleService.ListRoleAssignments", in)
out := new(ListRoleAssignmentsResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *roleService) AssignRoleToUser(ctx context.Context, in *AssignRoleToUserRequest, opts ...client.CallOption) (*AssignRoleToUserResponse, error) {
req := c.c.NewRequest(c.name, "RoleService.AssignRoleToUser", in)
out := new(AssignRoleToUserResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *roleService) RemoveRoleFromUser(ctx context.Context, in *RemoveRoleFromUserRequest, opts ...client.CallOption) (*empty.Empty, error) {
req := c.c.NewRequest(c.name, "RoleService.RemoveRoleFromUser", in)
out := new(empty.Empty)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for RoleService service
type RoleServiceHandler interface {
ListRoles(context.Context, *ListBundlesRequest, *ListBundlesResponse) error
ListRoleAssignments(context.Context, *ListRoleAssignmentsRequest, *ListRoleAssignmentsResponse) error
AssignRoleToUser(context.Context, *AssignRoleToUserRequest, *AssignRoleToUserResponse) error
RemoveRoleFromUser(context.Context, *RemoveRoleFromUserRequest, *empty.Empty) error
}
func RegisterRoleServiceHandler(s server.Server, hdlr RoleServiceHandler, opts ...server.HandlerOption) error {
type roleService interface {
ListRoles(ctx context.Context, in *ListBundlesRequest, out *ListBundlesResponse) error
ListRoleAssignments(ctx context.Context, in *ListRoleAssignmentsRequest, out *ListRoleAssignmentsResponse) error
AssignRoleToUser(ctx context.Context, in *AssignRoleToUserRequest, out *AssignRoleToUserResponse) error
RemoveRoleFromUser(ctx context.Context, in *RemoveRoleFromUserRequest, out *empty.Empty) error
}
type RoleService struct {
roleService
}
h := &roleServiceHandler{hdlr}
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "RoleService.ListRoles",
Path: []string{"/api/v0/settings/roles-list"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "RoleService.ListRoleAssignments",
Path: []string{"/api/v0/settings/assignments-list"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "RoleService.AssignRoleToUser",
Path: []string{"/api/v0/settings/assignments-add"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "RoleService.RemoveRoleFromUser",
Path: []string{"/api/v0/settings/assignments-remove"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
return s.Handle(s.NewHandler(&RoleService{h}, opts...))
}
type roleServiceHandler struct {
RoleServiceHandler
}
func (h *roleServiceHandler) ListRoles(ctx context.Context, in *ListBundlesRequest, out *ListBundlesResponse) error {
return h.RoleServiceHandler.ListRoles(ctx, in, out)
}
func (h *roleServiceHandler) ListRoleAssignments(ctx context.Context, in *ListRoleAssignmentsRequest, out *ListRoleAssignmentsResponse) error {
return h.RoleServiceHandler.ListRoleAssignments(ctx, in, out)
}
func (h *roleServiceHandler) AssignRoleToUser(ctx context.Context, in *AssignRoleToUserRequest, out *AssignRoleToUserResponse) error {
return h.RoleServiceHandler.AssignRoleToUser(ctx, in, out)
}
func (h *roleServiceHandler) RemoveRoleFromUser(ctx context.Context, in *RemoveRoleFromUserRequest, out *empty.Empty) error {
return h.RoleServiceHandler.RemoveRoleFromUser(ctx, in, out)
}
// Api Endpoints for PermissionService service
func NewPermissionServiceEndpoints() []*api.Endpoint {
return []*api.Endpoint{
&api.Endpoint{
Name: "PermissionService.ListPermissionsByResource",
Path: []string{"/api/v0/settings/permissions-list-by-resource"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
&api.Endpoint{
Name: "PermissionService.GetPermissionByID",
Path: []string{"/api/v0/settings/permissions-get-by-id"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
},
}
}
// Client API for PermissionService service
type PermissionService interface {
ListPermissionsByResource(ctx context.Context, in *ListPermissionsByResourceRequest, opts ...client.CallOption) (*ListPermissionsByResourceResponse, error)
GetPermissionByID(ctx context.Context, in *GetPermissionByIDRequest, opts ...client.CallOption) (*GetPermissionByIDResponse, error)
}
type permissionService struct {
c client.Client
name string
}
func NewPermissionService(name string, c client.Client) PermissionService {
return &permissionService{
c: c,
name: name,
}
}
func (c *permissionService) ListPermissionsByResource(ctx context.Context, in *ListPermissionsByResourceRequest, opts ...client.CallOption) (*ListPermissionsByResourceResponse, error) {
req := c.c.NewRequest(c.name, "PermissionService.ListPermissionsByResource", in)
out := new(ListPermissionsByResourceResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *permissionService) GetPermissionByID(ctx context.Context, in *GetPermissionByIDRequest, opts ...client.CallOption) (*GetPermissionByIDResponse, error) {
req := c.c.NewRequest(c.name, "PermissionService.GetPermissionByID", in)
out := new(GetPermissionByIDResponse)
err := c.c.Call(ctx, req, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for PermissionService service
type PermissionServiceHandler interface {
ListPermissionsByResource(context.Context, *ListPermissionsByResourceRequest, *ListPermissionsByResourceResponse) error
GetPermissionByID(context.Context, *GetPermissionByIDRequest, *GetPermissionByIDResponse) error
}
func RegisterPermissionServiceHandler(s server.Server, hdlr PermissionServiceHandler, opts ...server.HandlerOption) error {
type permissionService interface {
ListPermissionsByResource(ctx context.Context, in *ListPermissionsByResourceRequest, out *ListPermissionsByResourceResponse) error
GetPermissionByID(ctx context.Context, in *GetPermissionByIDRequest, out *GetPermissionByIDResponse) error
}
type PermissionService struct {
permissionService
}
h := &permissionServiceHandler{hdlr}
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "PermissionService.ListPermissionsByResource",
Path: []string{"/api/v0/settings/permissions-list-by-resource"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
opts = append(opts, api.WithEndpoint(&api.Endpoint{
Name: "PermissionService.GetPermissionByID",
Path: []string{"/api/v0/settings/permissions-get-by-id"},
Method: []string{"POST"},
Body: "*",
Handler: "rpc",
}))
return s.Handle(s.NewHandler(&PermissionService{h}, opts...))
}
type permissionServiceHandler struct {
PermissionServiceHandler
}
func (h *permissionServiceHandler) ListPermissionsByResource(ctx context.Context, in *ListPermissionsByResourceRequest, out *ListPermissionsByResourceResponse) error {
return h.PermissionServiceHandler.ListPermissionsByResource(ctx, in, out)
}
func (h *permissionServiceHandler) GetPermissionByID(ctx context.Context, in *GetPermissionByIDRequest, out *GetPermissionByIDResponse) error {
return h.PermissionServiceHandler.GetPermissionByID(ctx, in, out)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,414 @@
syntax = "proto3";
package proto;
option go_package = "pkg/proto/v0;proto";
import "google/api/annotations.proto";
import "protoc-gen-swagger/options/annotations.proto";
import "google/protobuf/empty.proto";
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
info: {
title: "Settings";
version: "1.0";
contact: {
name: "ownCloud GmbH";
url: "https://github.com/owncloud/ocis-settings";
email: "support@owncloud.com";
};
license: {
name: "Apache-2.0";
url: "https://github.com/owncloud/ocis-settings/blob/master/LICENSE";
};
};
schemes: HTTP;
schemes: HTTPS;
consumes: "application/json";
produces: "application/json";
external_docs: {
description: "Developer Manual";
url: "http://owncloud.github.io/extensions/ocis_settings/";
};
};
service BundleService {
rpc SaveBundle(SaveBundleRequest) returns (SaveBundleResponse) {
option (google.api.http) = {
post: "/api/v0/settings/bundle-save",
body: "*"
};
}
rpc GetBundle(GetBundleRequest) returns (GetBundleResponse) {
option (google.api.http) = {
post: "/api/v0/settings/bundle-get",
body: "*"
};
}
rpc ListBundles(ListBundlesRequest) returns (ListBundlesResponse) {
option (google.api.http) = {
post: "/api/v0/settings/bundles-list",
body: "*"
};
}
rpc AddSettingToBundle(AddSettingToBundleRequest) returns (AddSettingToBundleResponse) {
option (google.api.http) = {
post: "/api/v0/settings/bundles-add-setting",
body: "*"
};
}
rpc RemoveSettingFromBundle(RemoveSettingFromBundleRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
post: "/api/v0/settings/bundles-remove-setting",
body: "*"
};
}
}
service ValueService {
rpc SaveValue(SaveValueRequest) returns (SaveValueResponse) {
option (google.api.http) = {
post: "/api/v0/settings/values-save",
body: "*"
};
}
rpc GetValue(GetValueRequest) returns (GetValueResponse) {
option (google.api.http) = {
post: "/api/v0/settings/values-get",
body: "*"
};
}
rpc ListValues(ListValuesRequest) returns (ListValuesResponse) {
option (google.api.http) = {
post: "/api/v0/settings/values-list",
body: "*"
};
}
rpc GetValueByUniqueIdentifiers(GetValueByUniqueIdentifiersRequest) returns (GetValueResponse) {
option (google.api.http) = {
post: "/api/v0/settings/values-get-by-unique-identifiers",
body: "*"
};
}
}
service RoleService {
rpc ListRoles(ListBundlesRequest) returns (ListBundlesResponse) {
option (google.api.http) = {
post: "/api/v0/settings/roles-list",
body: "*"
};
}
rpc ListRoleAssignments(ListRoleAssignmentsRequest) returns (ListRoleAssignmentsResponse) {
option (google.api.http) = {
post: "/api/v0/settings/assignments-list",
body: "*"
};
}
rpc AssignRoleToUser(AssignRoleToUserRequest) returns (AssignRoleToUserResponse) {
option (google.api.http) = {
post: "/api/v0/settings/assignments-add",
body: "*"
};
}
rpc RemoveRoleFromUser(RemoveRoleFromUserRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {
post: "/api/v0/settings/assignments-remove",
body: "*"
};
}
}
service PermissionService {
rpc ListPermissionsByResource(ListPermissionsByResourceRequest) returns (ListPermissionsByResourceResponse) {
option (google.api.http) = {
post: "/api/v0/settings/permissions-list-by-resource",
body: "*"
};
}
rpc GetPermissionByID(GetPermissionByIDRequest) returns (GetPermissionByIDResponse) {
option (google.api.http) = {
post: "/api/v0/settings/permissions-get-by-id",
body: "*"
};
}
}
// ---
// requests and responses for settings bundles
// ---
message SaveBundleRequest {
Bundle bundle = 1;
}
message SaveBundleResponse {
Bundle bundle = 1;
}
message GetBundleRequest {
string bundle_id = 1;
}
message GetBundleResponse {
Bundle bundle = 1;
}
message ListBundlesRequest {
repeated string bundle_ids = 1;
}
message ListBundlesResponse {
repeated Bundle bundles = 1;
}
message AddSettingToBundleRequest {
string bundle_id = 1;
Setting setting = 2;
}
message AddSettingToBundleResponse {
Setting setting = 1;
}
message RemoveSettingFromBundleRequest {
string bundle_id = 1;
string setting_id = 2;
}
// ---
// requests and responses for settings values
// ---
message SaveValueRequest {
Value value = 1;
}
message SaveValueResponse {
ValueWithIdentifier value = 1;
}
message GetValueRequest {
string id = 1;
}
message GetValueResponse {
ValueWithIdentifier value = 1;
}
message ListValuesRequest {
string bundle_id = 1;
string account_uuid = 2;
}
message ListValuesResponse {
repeated ValueWithIdentifier values = 1;
}
message GetValueByUniqueIdentifiersRequest{
string account_uuid = 1;
string setting_id = 2;
}
message ValueWithIdentifier {
Identifier identifier = 1;
Value value = 2;
}
message Identifier {
string extension = 1;
string bundle = 2;
string setting = 3;
}
// --
// requests and responses for role assignments
// ---
message ListRoleAssignmentsRequest {
string account_uuid = 1;
}
message ListRoleAssignmentsResponse {
repeated UserRoleAssignment assignments = 1;
}
message AssignRoleToUserRequest {
string account_uuid = 1;
// the role_id is a bundle_id internally
string role_id = 2;
}
message AssignRoleToUserResponse {
UserRoleAssignment assignment = 1;
}
message RemoveRoleFromUserRequest {
string id = 1;
}
message UserRoleAssignment {
// id is generated upon saving the assignment
string id = 1;
string account_uuid = 2;
// the role_id is a bundle_id internally
string role_id = 3;
}
// --
// requests and responses for permissions
// ---
message ListPermissionsByResourceRequest {
Resource resource = 1;
}
message ListPermissionsByResourceResponse {
repeated Permission permissions = 1;
}
message GetPermissionByIDRequest {
string permission_id = 1;
}
message GetPermissionByIDResponse {
Permission permission = 1;
}
// ---
// resource payloads
// ---
message Resource {
enum Type {
TYPE_UNKNOWN = 0;
TYPE_SYSTEM = 1;
TYPE_FILE = 2;
TYPE_SHARE = 3;
TYPE_SETTING = 4;
TYPE_BUNDLE = 5;
TYPE_USER = 6;
TYPE_GROUP = 7;
}
Type type = 1;
string id = 2;
}
// ---
// payloads for bundles
// ---
message Bundle {
enum Type {
TYPE_UNKNOWN = 0;
TYPE_DEFAULT = 1;
TYPE_ROLE = 2;
}
string id = 1;
string name = 2;
Type type = 3;
string extension = 4;
string display_name = 5;
repeated Setting settings = 6;
Resource resource = 7;
}
message Setting {
string id = 1;
string name = 2;
string display_name = 3;
string description = 4;
oneof value {
Int int_value = 5;
String string_value = 6;
Bool bool_value = 7;
SingleChoiceList single_choice_value = 8;
MultiChoiceList multi_choice_value = 9;
Permission permission_value = 10;
}
Resource resource = 11;
}
message Int {
int64 default = 1;
int64 min = 2;
int64 max = 3;
int64 step = 4;
string placeholder = 5;
}
message String {
string default = 1;
bool required = 2;
int32 min_length = 3;
int32 max_length = 4;
string placeholder = 5;
}
message Bool {
bool default = 1;
string label = 2;
}
message SingleChoiceList {
repeated ListOption options = 1;
}
message MultiChoiceList {
repeated ListOption options = 1;
}
message ListOption {
ListOptionValue value = 1;
bool default = 2;
string display_value = 3;
}
message Permission {
enum Operation {
OPERATION_UNKNOWN = 0;
OPERATION_CREATE = 1;
OPERATION_READ = 2;
OPERATION_UPDATE = 3;
OPERATION_DELETE = 4;
OPERATION_WRITE = 5;// WRITE is a combination of CREATE and UPDATE
OPERATION_READWRITE = 6;// READWRITE is a combination of READ and WRITE
}
Operation operation = 1;
enum Constraint {
CONSTRAINT_UNKNOWN = 0;
CONSTRAINT_OWN = 1;
CONSTRAINT_SHARED = 2;
CONSTRAINT_ALL = 3;
}
Constraint constraint = 2;
}
// ---
// payloads for values
// ---
message Value {
// id is the id of the Value. It is generated on saving it.
string id = 1;
string bundle_id = 2;
// setting_id is the id of the setting from within its bundle.
string setting_id = 3;
string account_uuid = 4;
Resource resource = 5;
oneof value {
bool bool_value = 6;
int64 int_value = 7;
string string_value = 8;
ListValue list_value = 9;
}
}
message ListValue {
repeated ListOptionValue values = 1;
}
message ListOptionValue {
oneof option {
string string_value = 1;
int64 int_value = 2;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
package debug
import (
"context"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-settings/pkg/config"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Context context.Context
Config *config.Config
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}

View File

@@ -0,0 +1,51 @@
package debug
import (
"io"
"net/http"
"github.com/owncloud/ocis-pkg/v2/service/debug"
"github.com/owncloud/ocis-settings/pkg/config"
"github.com/owncloud/ocis-settings/pkg/version"
)
// Server initializes the debug service and server.
func Server(opts ...Option) (*http.Server, error) {
options := newOptions(opts...)
return debug.NewService(
debug.Logger(options.Logger),
debug.Name("settings"),
debug.Version(version.String),
debug.Address(options.Config.Debug.Addr),
debug.Token(options.Config.Debug.Token),
debug.Pprof(options.Config.Debug.Pprof),
debug.Zpages(options.Config.Debug.Zpages),
debug.Health(health(options.Config)),
debug.Ready(ready(options.Config)),
), nil
}
// health implements the health check.
func health(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
// TODO(tboerger): check if services are up and running
_, _ = io.WriteString(w, http.StatusText(http.StatusOK))
}
}
// ready implements the ready check.
func ready(cfg *config.Config) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
// TODO(tboerger): check if services are up and running
_, _ = io.WriteString(w, http.StatusText(http.StatusOK))
}
}

View File

@@ -0,0 +1,76 @@
package grpc
import (
"context"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-settings/pkg/config"
"github.com/owncloud/ocis-settings/pkg/metrics"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Name string
Logger log.Logger
Context context.Context
Config *config.Config
Metrics *metrics.Metrics
Flags []cli.Flag
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Name provides a name for the service.
func Name(val string) Option {
return func(o *Options) {
o.Name = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
// Metrics provides a function to set the metrics option.
func Metrics(val *metrics.Metrics) Option {
return func(o *Options) {
o.Metrics = val
}
}
// Flags provides a function to set the flags option.
func Flags(val []cli.Flag) Option {
return func(o *Options) {
o.Flags = append(o.Flags, val...)
}
}

View File

@@ -0,0 +1,40 @@
package grpc
import (
"github.com/owncloud/ocis-pkg/v2/service/grpc"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
svc "github.com/owncloud/ocis-settings/pkg/service/v0"
"github.com/owncloud/ocis-settings/pkg/version"
)
// Server initializes a new go-micro service ready to run
func Server(opts ...Option) grpc.Service {
options := newOptions(opts...)
service := grpc.NewService(
grpc.Logger(options.Logger),
grpc.Name(options.Name),
grpc.Version(version.String),
grpc.Address(options.Config.GRPC.Addr),
grpc.Namespace(options.Config.GRPC.Namespace),
grpc.Context(options.Context),
grpc.Flags(options.Flags...),
)
handle := svc.NewService(options.Config, options.Logger)
if err := proto.RegisterBundleServiceHandler(service.Server(), handle); err != nil {
options.Logger.Fatal().Err(err).Msg("could not register Bundle service handler")
}
if err := proto.RegisterValueServiceHandler(service.Server(), handle); err != nil {
options.Logger.Fatal().Err(err).Msg("could not register Value service handler")
}
if err := proto.RegisterRoleServiceHandler(service.Server(), handle); err != nil {
options.Logger.Fatal().Err(err).Msg("could not register Role service handler")
}
if err := proto.RegisterPermissionServiceHandler(service.Server(), handle); err != nil {
options.Logger.Fatal().Err(err).Msg("could not register Permission service handler")
}
service.Init()
return service
}

View File

@@ -0,0 +1,76 @@
package http
import (
"context"
"github.com/micro/cli/v2"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-settings/pkg/config"
"github.com/owncloud/ocis-settings/pkg/metrics"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Name string
Logger log.Logger
Context context.Context
Config *config.Config
Metrics *metrics.Metrics
Flags []cli.Flag
}
// newOptions initializes the available default options.
func newOptions(opts ...Option) Options {
opt := Options{}
for _, o := range opts {
o(&opt)
}
return opt
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Name provides a name for the service.
func Name(val string) Option {
return func(o *Options) {
o.Name = val
}
}
// Context provides a function to set the context option.
func Context(val context.Context) Option {
return func(o *Options) {
o.Context = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
// Metrics provides a function to set the metrics option.
func Metrics(val *metrics.Metrics) Option {
return func(o *Options) {
o.Metrics = val
}
}
// Flags provides a function to set the flags option.
func Flags(val []cli.Flag) Option {
return func(o *Options) {
o.Flags = append(o.Flags, val...)
}
}

View File

@@ -0,0 +1,81 @@
package http
import (
"github.com/go-chi/chi"
"github.com/owncloud/ocis-pkg/v2/account"
"github.com/owncloud/ocis-pkg/v2/middleware"
"github.com/owncloud/ocis-pkg/v2/service/http"
"github.com/owncloud/ocis-settings/pkg/assets"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
svc "github.com/owncloud/ocis-settings/pkg/service/v0"
"github.com/owncloud/ocis-settings/pkg/version"
)
// Server initializes the http service and server.
func Server(opts ...Option) http.Service {
options := newOptions(opts...)
service := http.NewService(
http.Logger(options.Logger),
http.Name(options.Name),
http.Version(version.String),
http.Address(options.Config.HTTP.Addr),
http.Namespace(options.Config.HTTP.Namespace),
http.Context(options.Context),
http.Flags(options.Flags...),
)
handle := svc.NewService(options.Config, options.Logger)
{
handle = svc.NewInstrument(handle, options.Metrics)
handle = svc.NewLogging(handle, options.Logger)
handle = svc.NewTracing(handle)
}
mux := chi.NewMux()
mux.Use(middleware.RealIP)
mux.Use(middleware.RequestID)
mux.Use(middleware.Cache)
mux.Use(middleware.Cors)
mux.Use(middleware.Secure)
mux.Use(middleware.ExtractAccountUUID(
account.Logger(options.Logger),
account.JWTSecret(options.Config.TokenManager.JWTSecret)),
)
mux.Use(middleware.Version(
options.Name,
version.String,
))
mux.Use(middleware.Logger(
options.Logger,
))
mux.Use(middleware.Static(
options.Config.HTTP.Root,
assets.New(
assets.Logger(options.Logger),
assets.Config(options.Config),
),
))
mux.Route(options.Config.HTTP.Root, func(r chi.Router) {
proto.RegisterBundleServiceWeb(r, handle)
proto.RegisterValueServiceWeb(r, handle)
proto.RegisterRoleServiceWeb(r, handle)
proto.RegisterPermissionServiceWeb(r, handle)
})
service.Handle(
"/",
mux,
)
if err := service.Init(); err != nil {
panic(err)
}
return service
}

View File

@@ -0,0 +1,13 @@
package svc
import (
"github.com/owncloud/ocis-settings/pkg/metrics"
)
// NewInstrument returns a service that instruments metrics.
func NewInstrument(next Service, metrics *metrics.Metrics) Service {
return Service{
manager: next.manager,
config: next.config,
}
}

View File

@@ -0,0 +1,13 @@
package svc
import (
"github.com/owncloud/ocis-pkg/v2/log"
)
// NewLogging returns a service that logs messages.
func NewLogging(next Service, logger log.Logger) Service {
return Service{
manager: next.manager,
config: next.config,
}
}

View File

@@ -0,0 +1,39 @@
package svc
import (
"net/http"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-settings/pkg/config"
)
// Option defines a single option function.
type Option func(o *Options)
// Options defines the available options for this package.
type Options struct {
Logger log.Logger
Config *config.Config
Middleware []func(http.Handler) http.Handler
}
// Logger provides a function to set the logger option.
func Logger(val log.Logger) Option {
return func(o *Options) {
o.Logger = val
}
}
// Config provides a function to set the config option.
func Config(val *config.Config) Option {
return func(o *Options) {
o.Config = val
}
}
// Middleware provides a function to set the middleware option.
func Middleware(val ...func(http.Handler) http.Handler) Option {
return func(o *Options) {
o.Middleware = val
}
}

View File

@@ -0,0 +1,57 @@
package svc
import "github.com/owncloud/ocis-settings/pkg/proto/v0"
func (g Service) hasPermission(
roleIDs []string,
resource *proto.Resource,
operations []proto.Permission_Operation,
constraint proto.Permission_Constraint,
) bool {
permissions, err := g.manager.ListPermissionsByResource(resource, roleIDs)
if err != nil {
g.logger.Debug().Err(err).
Str("resource-type", resource.Type.String()).
Str("resource-id", resource.Id).
Msg("permissions could not be loaded for resource")
return false
}
permissions = getFilteredPermissionsByOperations(permissions, operations)
return isConstraintFulfilled(permissions, constraint)
}
// filterPermissionsByOperations returns the subset of the given permissions, where at least one of the given operations is fulfilled.
func getFilteredPermissionsByOperations(permissions []*proto.Permission, operations []proto.Permission_Operation) []*proto.Permission {
var filteredPermissions []*proto.Permission
for _, permission := range permissions {
if isAnyOperationFulfilled(permission, operations) {
filteredPermissions = append(filteredPermissions, permission)
}
}
return filteredPermissions
}
// isAnyOperationFulfilled checks if the permissions is about any of the operations
func isAnyOperationFulfilled(permission *proto.Permission, operations []proto.Permission_Operation) bool {
for _, operation := range operations {
if operation == permission.Operation {
return true
}
}
return false
}
// isConstraintFulfilled checks if one of the permissions has the same or a parent of the constraint.
// this is only a comparison on ENUM level. More sophisticated checks cannot happen here...
func isConstraintFulfilled(permissions []*proto.Permission, constraint proto.Permission_Constraint) bool {
for _, permission := range permissions {
// comparing enum by order is not a feasible solution, because `SHARED` is not a superset of `OWN`.
if permission.Constraint == proto.Permission_CONSTRAINT_ALL {
return true
}
if permission.Constraint != proto.Permission_CONSTRAINT_UNKNOWN && permission.Constraint == constraint {
return true
}
}
return false
}

View File

@@ -0,0 +1,388 @@
package svc
import (
"context"
"fmt"
"github.com/golang/protobuf/ptypes/empty"
merrors "github.com/micro/go-micro/v2/errors"
"github.com/micro/go-micro/v2/metadata"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-pkg/v2/middleware"
"github.com/owncloud/ocis-pkg/v2/roles"
"github.com/owncloud/ocis-settings/pkg/config"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
"github.com/owncloud/ocis-settings/pkg/settings"
store "github.com/owncloud/ocis-settings/pkg/store/filesystem"
)
// Service represents a service.
type Service struct {
id string
config *config.Config
logger log.Logger
manager settings.Manager
}
// NewService returns a service implementation for Service.
func NewService(cfg *config.Config, logger log.Logger) Service {
service := Service{
id: "ocis-settings",
config: cfg,
logger: logger,
manager: store.New(cfg),
}
service.RegisterDefaultRoles()
return service
}
// RegisterDefaultRoles composes default roles and saves them. Skipped if the roles already exist.
func (g Service) RegisterDefaultRoles() {
// FIXME: we're writing default roles per service start (i.e. twice at the moment, for http and grpc server). has to happen only once.
for _, role := range generateBundlesDefaultRoles() {
bundleID := role.Extension + "." + role.Id
// check if the role already exists
bundle, _ := g.manager.ReadBundle(role.Id)
if bundle != nil {
g.logger.Debug().Str("bundleID", bundleID).Msg("bundle already exists. skipping.")
continue
}
// create the role
_, err := g.manager.WriteBundle(role)
if err != nil {
g.logger.Error().Err(err).Str("bundleID", bundleID).Msg("failed to register bundle")
}
g.logger.Debug().Str("bundleID", bundleID).Msg("successfully registered bundle")
}
}
// TODO: check permissions on every request
// SaveBundle implements the BundleServiceHandler interface
func (g Service) SaveBundle(c context.Context, req *proto.SaveBundleRequest, res *proto.SaveBundleResponse) error {
cleanUpResource(c, req.Bundle.Resource)
if validationError := validateSaveBundle(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
r, err := g.manager.WriteBundle(req.Bundle)
if err != nil {
return merrors.BadRequest(g.id, "%s", err)
}
res.Bundle = r
return nil
}
// GetBundle implements the BundleServiceHandler interface
func (g Service) GetBundle(c context.Context, req *proto.GetBundleRequest, res *proto.GetBundleResponse) error {
if validationError := validateGetBundle(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
bundle, err := g.manager.ReadBundle(req.BundleId)
if err != nil {
return merrors.NotFound(g.id, "%s", err)
}
filteredBundle := g.getFilteredBundle(g.getRoleIDs(c), bundle)
if len(filteredBundle.Settings) == 0 {
err = fmt.Errorf("could not read bundle: %s", req.BundleId)
return merrors.NotFound(g.id, "%s", err)
}
res.Bundle = filteredBundle
return nil
}
// ListBundles implements the BundleServiceHandler interface
func (g Service) ListBundles(c context.Context, req *proto.ListBundlesRequest, res *proto.ListBundlesResponse) error {
// fetch all bundles
if validationError := validateListBundles(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
bundles, err := g.manager.ListBundles(proto.Bundle_TYPE_DEFAULT, req.BundleIds)
if err != nil {
return merrors.NotFound(g.id, "%s", err)
}
roleIDs := g.getRoleIDs(c)
// filter settings in bundles that are allowed according to roles
var filteredBundles []*proto.Bundle
for _, bundle := range bundles {
filteredBundle := g.getFilteredBundle(roleIDs, bundle)
if len(filteredBundle.Settings) > 0 {
filteredBundles = append(filteredBundles, filteredBundle)
}
}
res.Bundles = filteredBundles
return nil
}
func (g Service) getFilteredBundle(roleIDs []string, bundle *proto.Bundle) *proto.Bundle {
// check if full bundle is whitelisted
bundleResource := &proto.Resource{
Type: proto.Resource_TYPE_BUNDLE,
Id: bundle.Id,
}
if g.hasPermission(
roleIDs,
bundleResource,
[]proto.Permission_Operation{proto.Permission_OPERATION_READ, proto.Permission_OPERATION_READWRITE},
proto.Permission_CONSTRAINT_OWN,
) {
return bundle
}
// filter settings based on permissions
var filteredSettings []*proto.Setting
for _, setting := range bundle.Settings {
settingResource := &proto.Resource{
Type: proto.Resource_TYPE_SETTING,
Id: setting.Id,
}
if g.hasPermission(
roleIDs,
settingResource,
[]proto.Permission_Operation{proto.Permission_OPERATION_READ, proto.Permission_OPERATION_READWRITE},
proto.Permission_CONSTRAINT_OWN,
) {
filteredSettings = append(filteredSettings, setting)
}
}
bundle.Settings = filteredSettings
return bundle
}
// AddSettingToBundle implements the BundleServiceHandler interface
func (g Service) AddSettingToBundle(c context.Context, req *proto.AddSettingToBundleRequest, res *proto.AddSettingToBundleResponse) error {
cleanUpResource(c, req.Setting.Resource)
if validationError := validateAddSettingToBundle(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
r, err := g.manager.AddSettingToBundle(req.BundleId, req.Setting)
if err != nil {
return merrors.BadRequest(g.id, "%s", err)
}
res.Setting = r
return nil
}
// RemoveSettingFromBundle implements the BundleServiceHandler interface
func (g Service) RemoveSettingFromBundle(c context.Context, req *proto.RemoveSettingFromBundleRequest, _ *empty.Empty) error {
if validationError := validateRemoveSettingFromBundle(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
if err := g.manager.RemoveSettingFromBundle(req.BundleId, req.SettingId); err != nil {
return merrors.BadRequest(g.id, "%s", err)
}
return nil
}
// SaveValue implements the ValueServiceHandler interface
func (g Service) SaveValue(c context.Context, req *proto.SaveValueRequest, res *proto.SaveValueResponse) error {
req.Value.AccountUuid = getValidatedAccountUUID(c, req.Value.AccountUuid)
cleanUpResource(c, req.Value.Resource)
// TODO: we need to check, if the authenticated user has permission to write the value for the specified resource (e.g. global, file with id xy, ...)
if validationError := validateSaveValue(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
r, err := g.manager.WriteValue(req.Value)
if err != nil {
return merrors.BadRequest(g.id, "%s", err)
}
valueWithIdentifier, err := g.getValueWithIdentifier(r)
if err != nil {
return merrors.NotFound(g.id, "%s", err)
}
res.Value = valueWithIdentifier
return nil
}
// GetValue implements the ValueServiceHandler interface
func (g Service) GetValue(c context.Context, req *proto.GetValueRequest, res *proto.GetValueResponse) error {
if validationError := validateGetValue(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
r, err := g.manager.ReadValue(req.Id)
if err != nil {
return merrors.NotFound(g.id, "%s", err)
}
valueWithIdentifier, err := g.getValueWithIdentifier(r)
if err != nil {
return merrors.NotFound(g.id, "%s", err)
}
res.Value = valueWithIdentifier
return nil
}
// GetValueByUniqueIdentifiers implements the ValueService interface
func (g Service) GetValueByUniqueIdentifiers(ctx context.Context, req *proto.GetValueByUniqueIdentifiersRequest, res *proto.GetValueResponse) error {
if validationError := validateGetValueByUniqueIdentifiers(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
v, err := g.manager.ReadValueByUniqueIdentifiers(req.AccountUuid, req.SettingId)
if err != nil {
return merrors.NotFound(g.id, "%s", err)
}
if v.BundleId != "" {
valueWithIdentifier, err := g.getValueWithIdentifier(v)
if err != nil {
return merrors.NotFound(g.id, "%s", err)
}
res.Value = valueWithIdentifier
}
return nil
}
// ListValues implements the ValueServiceHandler interface
func (g Service) ListValues(c context.Context, req *proto.ListValuesRequest, res *proto.ListValuesResponse) error {
req.AccountUuid = getValidatedAccountUUID(c, req.AccountUuid)
if validationError := validateListValues(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
r, err := g.manager.ListValues(req.BundleId, req.AccountUuid)
if err != nil {
return merrors.NotFound(g.id, "%s", err)
}
var result []*proto.ValueWithIdentifier
for _, value := range r {
valueWithIdentifier, err := g.getValueWithIdentifier(value)
if err == nil {
result = append(result, valueWithIdentifier)
}
}
res.Values = result
return nil
}
// ListRoles implements the RoleServiceHandler interface
func (g Service) ListRoles(c context.Context, req *proto.ListBundlesRequest, res *proto.ListBundlesResponse) error {
//accountUUID := getValidatedAccountUUID(c, "me")
if validationError := validateListRoles(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
r, err := g.manager.ListBundles(proto.Bundle_TYPE_ROLE, req.BundleIds)
if err != nil {
return merrors.NotFound(g.id, "%s", err)
}
// TODO: only allow to list roles when user has account/role/... management permissions
res.Bundles = r
return nil
}
// ListRoleAssignments implements the RoleServiceHandler interface
func (g Service) ListRoleAssignments(c context.Context, req *proto.ListRoleAssignmentsRequest, res *proto.ListRoleAssignmentsResponse) error {
req.AccountUuid = getValidatedAccountUUID(c, req.AccountUuid)
if validationError := validateListRoleAssignments(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
r, err := g.manager.ListRoleAssignments(req.AccountUuid)
if err != nil {
return merrors.NotFound(g.id, "%s", err)
}
res.Assignments = r
return nil
}
// AssignRoleToUser implements the RoleServiceHandler interface
func (g Service) AssignRoleToUser(c context.Context, req *proto.AssignRoleToUserRequest, res *proto.AssignRoleToUserResponse) error {
req.AccountUuid = getValidatedAccountUUID(c, req.AccountUuid)
if validationError := validateAssignRoleToUser(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
r, err := g.manager.WriteRoleAssignment(req.AccountUuid, req.RoleId)
if err != nil {
return merrors.BadRequest(g.id, "%s", err)
}
res.Assignment = r
return nil
}
// RemoveRoleFromUser implements the RoleServiceHandler interface
func (g Service) RemoveRoleFromUser(c context.Context, req *proto.RemoveRoleFromUserRequest, _ *empty.Empty) error {
if validationError := validateRemoveRoleFromUser(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
if err := g.manager.RemoveRoleAssignment(req.Id); err != nil {
return merrors.BadRequest(g.id, "%s", err)
}
return nil
}
// ListPermissionsByResource implements the PermissionServiceHandler interface
func (g Service) ListPermissionsByResource(c context.Context, req *proto.ListPermissionsByResourceRequest, res *proto.ListPermissionsByResourceResponse) error {
if validationError := validateListPermissionsByResource(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
permissions, err := g.manager.ListPermissionsByResource(req.Resource, g.getRoleIDs(c))
if err != nil {
return merrors.BadRequest(g.id, "%s", err)
}
res.Permissions = permissions
return nil
}
// GetPermissionByID implements the PermissionServiceHandler interface
func (g Service) GetPermissionByID(c context.Context, req *proto.GetPermissionByIDRequest, res *proto.GetPermissionByIDResponse) error {
if validationError := validateGetPermissionByID(req); validationError != nil {
return merrors.BadRequest(g.id, "%s", validationError)
}
permission, err := g.manager.ReadPermissionByID(req.PermissionId, g.getRoleIDs(c))
if err != nil {
return merrors.BadRequest(g.id, "%s", err)
}
if permission == nil {
return merrors.NotFound(g.id, "%s", fmt.Errorf("permission %s not found in roles", req.PermissionId))
}
res.Permission = permission
return nil
}
// cleanUpResource makes sure that the account uuid of the authenticated user is injected if needed.
func cleanUpResource(c context.Context, resource *proto.Resource) {
if resource != nil && resource.Type == proto.Resource_TYPE_USER {
resource.Id = getValidatedAccountUUID(c, resource.Id)
}
}
// getValidatedAccountUUID converts `me` into an actual account uuid from the context, if possible.
// the result of this function will always be a valid lower-case UUID or an empty string.
func getValidatedAccountUUID(c context.Context, accountUUID string) string {
if accountUUID == "me" {
if ownAccountUUID, ok := metadata.Get(c, middleware.AccountID); ok {
accountUUID = ownAccountUUID
}
}
if accountUUID == "me" {
// no matter what happens above, an accountUUID of `me` must not be passed on. Clear it instead.
accountUUID = ""
}
return accountUUID
}
// getRoleIDs extracts the roleIDs of the authenticated user from the context.
func (g Service) getRoleIDs(c context.Context) []string {
if ownRoleIDs, ok := roles.ReadRoleIDsFromContext(c); ok {
return ownRoleIDs
}
return []string{}
}
func (g Service) getValueWithIdentifier(value *proto.Value) (*proto.ValueWithIdentifier, error) {
bundle, err := g.manager.ReadBundle(value.BundleId)
if err != nil {
return nil, err
}
setting, err := g.manager.ReadSetting(value.SettingId)
if err != nil {
return nil, err
}
return &proto.ValueWithIdentifier{
Identifier: &proto.Identifier{
Extension: bundle.Extension,
Bundle: bundle.Name,
Setting: setting.Name,
},
Value: value,
}, nil
}

View File

@@ -0,0 +1,61 @@
package svc
import (
"context"
"testing"
"github.com/micro/go-micro/v2/metadata"
"github.com/owncloud/ocis-pkg/v2/middleware"
"github.com/stretchr/testify/assert"
)
var (
ctxWithUUID = metadata.Set(context.Background(), middleware.AccountID, "61445573-4dbe-4d56-88dc-88ab47aceba7")
ctxWithEmptyUUID = metadata.Set(context.Background(), middleware.AccountID, "")
emptyCtx = context.Background()
scenarios = []struct {
name string
accountUUID string
ctx context.Context
expect string
}{
{
name: "context with UUID; identifier = 'me'",
ctx: ctxWithUUID,
accountUUID: "me",
expect: "61445573-4dbe-4d56-88dc-88ab47aceba7",
},
{
name: "context with empty UUID; identifier = 'me'",
ctx: ctxWithEmptyUUID,
accountUUID: "me",
expect: "",
},
{
name: "context without UUID; identifier = 'me'",
ctx: emptyCtx,
accountUUID: "me",
expect: "",
},
{
name: "context with UUID; identifier not 'me'",
ctx: ctxWithUUID,
accountUUID: "",
expect: "",
},
}
)
func TestGetValidatedAccountUUID(t *testing.T) {
for _, s := range scenarios {
scenario := s
t.Run(scenario.name, func(t *testing.T) {
got := getValidatedAccountUUID(scenario.ctx, scenario.accountUUID)
assert.NotPanics(t, func() {
getValidatedAccountUUID(emptyCtx, scenario.accountUUID)
})
assert.Equal(t, scenario.expect, got)
})
}
}

View File

@@ -0,0 +1,65 @@
package svc
import settings "github.com/owncloud/ocis-settings/pkg/proto/v0"
const (
// BundleUUIDRoleAdmin represents the admin role
BundleUUIDRoleAdmin = "71881883-1768-46bd-a24d-a356a2afdf7f"
// BundleUUIDRoleUser represents the user role.
BundleUUIDRoleUser = "d7beeea8-8ff4-406b-8fb6-ab2dd81e6b11"
// BundleUUIDRoleGuest represents the guest role.
BundleUUIDRoleGuest = "38071a68-456a-4553-846a-fa67bf5596cc"
)
// generateBundlesDefaultRoles bootstraps the default roles.
func generateBundlesDefaultRoles() []*settings.Bundle {
return []*settings.Bundle{
generateBundleAdminRole(),
generateBundleUserRole(),
generateBundleGuestRole(),
}
}
func generateBundleAdminRole() *settings.Bundle {
return &settings.Bundle{
Id: BundleUUIDRoleAdmin,
Name: "admin",
Type: settings.Bundle_TYPE_ROLE,
Extension: "ocis-roles",
DisplayName: "Admin",
Resource: &settings.Resource{
Type: settings.Resource_TYPE_SYSTEM,
},
Settings: []*settings.Setting{},
}
}
func generateBundleUserRole() *settings.Bundle {
return &settings.Bundle{
Id: BundleUUIDRoleUser,
Name: "user",
Type: settings.Bundle_TYPE_ROLE,
Extension: "ocis-roles",
DisplayName: "User",
Resource: &settings.Resource{
Type: settings.Resource_TYPE_SYSTEM,
},
Settings: []*settings.Setting{},
}
}
func generateBundleGuestRole() *settings.Bundle {
return &settings.Bundle{
Id: BundleUUIDRoleGuest,
Name: "guest",
Type: settings.Bundle_TYPE_ROLE,
Extension: "ocis-roles",
DisplayName: "Guest",
Resource: &settings.Resource{
Type: settings.Resource_TYPE_SYSTEM,
},
Settings: []*settings.Setting{},
}
}

View File

@@ -0,0 +1,9 @@
package svc
// NewTracing returns a service that instruments traces.
func NewTracing(next Service) Service {
return Service{
manager: next.manager,
config: next.config,
}
}

View File

@@ -0,0 +1,163 @@
package svc
import (
"regexp"
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/go-ozzo/ozzo-validation/v4/is"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
)
var (
regexForAccountUUID = regexp.MustCompile(`^[A-Za-z0-9\-_.+@]+$`)
requireAccountID = []validation.Rule{
// use rule for validation error message consistency (".. must not be blank" on empty strings)
validation.Required,
validation.Match(regexForAccountUUID),
}
regexForKeys = regexp.MustCompile(`^[A-Za-z0-9\-_]*$`)
requireAlphanumeric = []validation.Rule{
validation.Required,
validation.Match(regexForKeys),
}
)
func validateSaveBundle(req *proto.SaveBundleRequest) error {
if err := validation.ValidateStruct(
req.Bundle,
validation.Field(&req.Bundle.Id, validation.When(req.Bundle.Id != "", is.UUID)),
validation.Field(&req.Bundle.Name, requireAlphanumeric...),
validation.Field(&req.Bundle.Type, validation.NotIn(proto.Bundle_TYPE_UNKNOWN)),
validation.Field(&req.Bundle.Extension, requireAlphanumeric...),
validation.Field(&req.Bundle.DisplayName, validation.Required),
validation.Field(&req.Bundle.Settings, validation.Required),
); err != nil {
return err
}
if err := validateResource(req.Bundle.Resource); err != nil {
return err
}
for i := range req.Bundle.Settings {
if err := validateSetting(req.Bundle.Settings[i]); err != nil {
return err
}
}
return nil
}
func validateGetBundle(req *proto.GetBundleRequest) error {
return validation.Validate(&req.BundleId, is.UUID)
}
func validateListBundles(req *proto.ListBundlesRequest) error {
return nil
}
func validateAddSettingToBundle(req *proto.AddSettingToBundleRequest) error {
if err := validation.ValidateStruct(req, validation.Field(&req.BundleId, is.UUID)); err != nil {
return err
}
return validateSetting(req.Setting)
}
func validateRemoveSettingFromBundle(req *proto.RemoveSettingFromBundleRequest) error {
return validation.ValidateStruct(
req,
validation.Field(&req.BundleId, is.UUID),
validation.Field(&req.SettingId, is.UUID),
)
}
func validateSaveValue(req *proto.SaveValueRequest) error {
if err := validation.ValidateStruct(
req.Value,
validation.Field(&req.Value.Id, validation.When(req.Value.Id != "", is.UUID)),
validation.Field(&req.Value.BundleId, is.UUID),
validation.Field(&req.Value.SettingId, is.UUID),
validation.Field(&req.Value.AccountUuid, requireAccountID...),
); err != nil {
return err
}
if err := validateResource(req.Value.Resource); err != nil {
return err
}
// TODO: validate values against the respective setting. need to check if constraints of the setting are fulfilled.
return nil
}
func validateGetValue(req *proto.GetValueRequest) error {
return validation.Validate(req.Id, is.UUID)
}
func validateGetValueByUniqueIdentifiers(req *proto.GetValueByUniqueIdentifiersRequest) error {
return validation.ValidateStruct(
req,
validation.Field(&req.SettingId, is.UUID),
validation.Field(&req.AccountUuid, requireAccountID...),
)
}
func validateListValues(req *proto.ListValuesRequest) error {
return validation.ValidateStruct(
req,
validation.Field(&req.BundleId, validation.When(req.BundleId != "", is.UUID)),
validation.Field(&req.AccountUuid, validation.When(req.AccountUuid != "", validation.Match(regexForAccountUUID))),
)
}
func validateListRoles(req *proto.ListBundlesRequest) error {
return nil
}
func validateListRoleAssignments(req *proto.ListRoleAssignmentsRequest) error {
return validation.Validate(req.AccountUuid, requireAccountID...)
}
func validateAssignRoleToUser(req *proto.AssignRoleToUserRequest) error {
return validation.ValidateStruct(
req,
validation.Field(&req.AccountUuid, requireAccountID...),
validation.Field(&req.RoleId, is.UUID),
)
}
func validateRemoveRoleFromUser(req *proto.RemoveRoleFromUserRequest) error {
return validation.ValidateStruct(
req,
validation.Field(&req.Id, is.UUID),
)
}
func validateListPermissionsByResource(req *proto.ListPermissionsByResourceRequest) error {
return validateResource(req.Resource)
}
func validateGetPermissionByID(req *proto.GetPermissionByIDRequest) error {
return validation.ValidateStruct(
req,
validation.Field(&req.PermissionId, requireAlphanumeric...),
)
}
// validateResource is an internal helper for validating the content of a resource.
func validateResource(resource *proto.Resource) error {
if err := validation.Validate(&resource, validation.Required); err != nil {
return err
}
return validation.Validate(&resource, validation.NotIn(proto.Resource_TYPE_UNKNOWN))
}
// validateSetting is an internal helper for validating the content of a setting.
func validateSetting(setting *proto.Setting) error {
// TODO: make sanity checks, like for int settings, min <= default <= max.
if err := validation.ValidateStruct(
setting,
validation.Field(&setting.Id, validation.When(setting.Id != "", is.UUID)),
validation.Field(&setting.Name, requireAlphanumeric...),
); err != nil {
return err
}
return validateResource(setting.Resource)
}

View File

@@ -0,0 +1,53 @@
package settings
import (
"github.com/owncloud/ocis-settings/pkg/config"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
)
var (
// Registry uses the strategy pattern as a registry
Registry = map[string]RegisterFunc{}
)
// RegisterFunc stores store constructors
type RegisterFunc func(*config.Config) Manager
// Manager combines service interfaces for abstraction of storage implementations
type Manager interface {
BundleManager
ValueManager
RoleAssignmentManager
PermissionManager
}
// BundleManager is a bundle service interface for abstraction of storage implementations
type BundleManager interface {
ListBundles(bundleType proto.Bundle_Type, bundleIDs []string) ([]*proto.Bundle, error)
ReadBundle(bundleID string) (*proto.Bundle, error)
WriteBundle(bundle *proto.Bundle) (*proto.Bundle, error)
ReadSetting(settingID string) (*proto.Setting, error)
AddSettingToBundle(bundleID string, setting *proto.Setting) (*proto.Setting, error)
RemoveSettingFromBundle(bundleID, settingID string) error
}
// ValueManager is a value service interface for abstraction of storage implementations
type ValueManager interface {
ListValues(bundleID, accountUUID string) ([]*proto.Value, error)
ReadValue(valueID string) (*proto.Value, error)
ReadValueByUniqueIdentifiers(accountUUID, settingID string) (*proto.Value, error)
WriteValue(value *proto.Value) (*proto.Value, error)
}
// RoleAssignmentManager is a role assignment service interface for abstraction of storage implementations
type RoleAssignmentManager interface {
ListRoleAssignments(accountUUID string) ([]*proto.UserRoleAssignment, error)
WriteRoleAssignment(accountUUID, roleID string) (*proto.UserRoleAssignment, error)
RemoveRoleAssignment(assignmentID string) error
}
// PermissionManager is a permissions service interface for abstraction of storage implementations
type PermissionManager interface {
ListPermissionsByResource(resource *proto.Resource, roleIDs []string) ([]*proto.Permission, error)
ReadPermissionByID(permissionID string, roleIDs []string) (*proto.Permission, error)
}

View File

@@ -0,0 +1,67 @@
// Package store implements the go-micro store interface
package store
import (
"io/ioutil"
"os"
"path/filepath"
"github.com/gofrs/uuid"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
)
// ListRoleAssignments loads and returns all role assignments matching the given assignment identifier.
func (s Store) ListRoleAssignments(accountUUID string) ([]*proto.UserRoleAssignment, error) {
var records []*proto.UserRoleAssignment
assignmentsFolder := s.buildFolderPathForRoleAssignments(false)
assignmentFiles, err := ioutil.ReadDir(assignmentsFolder)
if err != nil {
return records, nil
}
for _, assignmentFile := range assignmentFiles {
record := proto.UserRoleAssignment{}
err = s.parseRecordFromFile(&record, filepath.Join(assignmentsFolder, assignmentFile.Name()))
if err == nil {
if record.AccountUuid == accountUUID {
records = append(records, &record)
}
}
}
return records, nil
}
// WriteRoleAssignment appends the given role assignment to the existing assignments of the respective account.
func (s Store) WriteRoleAssignment(accountUUID, roleID string) (*proto.UserRoleAssignment, error) {
// as per https://github.com/owncloud/product/issues/103 "Each user can have exactly one role"
list, err := s.ListRoleAssignments(accountUUID)
if err != nil {
return nil, err
}
if len(list) > 0 {
filePath := s.buildFilePathForRoleAssignment(list[0].Id, true)
if err := os.Remove(filePath); err != nil {
return nil, err
}
}
assignment := &proto.UserRoleAssignment{
Id: uuid.Must(uuid.NewV4()).String(),
AccountUuid: accountUUID,
RoleId: roleID,
}
filePath := s.buildFilePathForRoleAssignment(assignment.Id, true)
if err := s.writeRecordToFile(assignment, filePath); err != nil {
return nil, err
}
s.Logger.Debug().Msgf("request contents written to file: %v", filePath)
return assignment, nil
}
// RemoveRoleAssignment deletes the given role assignment from the existing assignments of the respective account.
func (s Store) RemoveRoleAssignment(assignmentID string) error {
filePath := s.buildFilePathForRoleAssignment(assignmentID, false)
return os.Remove(filePath)
}

View File

@@ -0,0 +1,178 @@
package store
import (
"errors"
"log"
"os"
"path/filepath"
"testing"
olog "github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
"github.com/stretchr/testify/assert"
)
var (
einstein = "a4d07560-a670-4be9-8d60-9b547751a208"
//marie = "3c054db3-eec1-4ca4-b985-bc56dcf560cb"
s = Store{
dataPath: dataRoot,
Logger: logger,
}
logger = olog.NewLogger(
olog.Color(true),
olog.Pretty(true),
olog.Level("info"),
)
bundles = []*proto.Bundle{
{
Id: "f36db5e6-a03c-40df-8413-711c67e40b47",
Type: proto.Bundle_TYPE_ROLE,
DisplayName: "test role - reads | update",
Name: "TEST_ROLE",
Extension: "ocis-settings",
Resource: &proto.Resource{
Type: proto.Resource_TYPE_BUNDLE,
},
Settings: []*proto.Setting{
{
Name: "update",
Value: &proto.Setting_PermissionValue{
PermissionValue: &proto.Permission{
Operation: proto.Permission_OPERATION_UPDATE,
},
},
},
{
Name: "read",
Value: &proto.Setting_PermissionValue{
PermissionValue: &proto.Permission{
Operation: proto.Permission_OPERATION_READ,
},
},
},
},
},
{
Id: "44f1a664-0a7f-461a-b0be-5b59e46bbc7a",
Type: proto.Bundle_TYPE_ROLE,
DisplayName: "another",
Name: "ANOTHER_TEST_ROLE",
Extension: "ocis-settings",
Resource: &proto.Resource{
Type: proto.Resource_TYPE_BUNDLE,
},
Settings: []*proto.Setting{
{
Name: "read",
Value: &proto.Setting_PermissionValue{
PermissionValue: &proto.Permission{
Operation: proto.Permission_OPERATION_READ,
},
},
},
},
},
}
)
func init() {
setupRoles()
}
func setupRoles() {
for i := range bundles {
if _, err := s.WriteBundle(bundles[i]); err != nil {
log.Fatal(err)
}
}
}
func TestAssignmentUniqueness(t *testing.T) {
var scenarios = []struct {
name string
userID string
firstRole string
secondRole string
}{
{
"roles assignments",
einstein,
"f36db5e6-a03c-40df-8413-711c67e40b47",
"44f1a664-0a7f-461a-b0be-5b59e46bbc7a",
},
}
for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.name, func(t *testing.T) {
firstAssignment, err := s.WriteRoleAssignment(scenario.userID, scenario.firstRole)
assert.NoError(t, err)
assert.Equal(t, firstAssignment.RoleId, scenario.firstRole)
assert.FileExists(t, filepath.Join(dataRoot, "assignments", firstAssignment.Id+".json"))
list, err := s.ListRoleAssignments(scenario.userID)
assert.NoError(t, err)
assert.Equal(t, 1, len(list))
// creating another assignment shouldn't add another entry, as we support max one role per user.
secondAssignment, err := s.WriteRoleAssignment(scenario.userID, scenario.secondRole)
assert.NoError(t, err)
assert.Equal(t, 1, len(list))
// assigning the second role should remove the old file and create a new one.
list, err = s.ListRoleAssignments(scenario.userID)
assert.NoError(t, err)
assert.Equal(t, 1, len(list))
assert.Equal(t, secondAssignment.RoleId, scenario.secondRole)
assert.NoFileExists(t, filepath.Join(dataRoot, "assignments", firstAssignment.Id+".json"))
assert.FileExists(t, filepath.Join(dataRoot, "assignments", secondAssignment.Id+".json"))
})
}
burnRoot()
}
func TestDeleteAssignment(t *testing.T) {
var scenarios = []struct {
name string
userID string
firstRole string
secondRole string
}{
{
"roles assignments",
einstein,
"f36db5e6-a03c-40df-8413-711c67e40b47",
"44f1a664-0a7f-461a-b0be-5b59e46bbc7a",
},
}
for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.name, func(t *testing.T) {
assignment, err := s.WriteRoleAssignment(scenario.userID, scenario.firstRole)
assert.NoError(t, err)
assert.Equal(t, assignment.RoleId, scenario.firstRole)
assert.FileExists(t, filepath.Join(dataRoot, "assignments", assignment.Id+".json"))
list, err := s.ListRoleAssignments(scenario.userID)
assert.NoError(t, err)
assert.Equal(t, 1, len(list))
err = s.RemoveRoleAssignment(assignment.Id)
assert.NoError(t, err)
list, err = s.ListRoleAssignments(scenario.userID)
assert.NoError(t, err)
assert.Equal(t, 0, len(list))
err = s.RemoveRoleAssignment(assignment.Id)
merr := &os.PathError{}
assert.Equal(t, true, errors.As(err, &merr))
})
}
burnRoot()
}

View File

@@ -0,0 +1,174 @@
// Package store implements the go-micro store interface
package store
import (
"fmt"
"io/ioutil"
"path/filepath"
"sync"
"github.com/gofrs/uuid"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
)
var m = &sync.RWMutex{}
// ListBundles returns all bundles in the dataPath folder that match the given type.
func (s Store) ListBundles(bundleType proto.Bundle_Type, bundleIDs []string) ([]*proto.Bundle, error) {
// FIXME: list requests should be ran against a cache, not FS
m.RLock()
defer m.RUnlock()
bundlesFolder := s.buildFolderPathForBundles(false)
bundleFiles, err := ioutil.ReadDir(bundlesFolder)
if err != nil {
return []*proto.Bundle{}, nil
}
records := make([]*proto.Bundle, 0, len(bundleFiles))
for _, bundleFile := range bundleFiles {
record := proto.Bundle{}
err = s.parseRecordFromFile(&record, filepath.Join(bundlesFolder, bundleFile.Name()))
if err != nil {
s.Logger.Warn().Msgf("error reading %v", bundleFile)
continue
}
if record.Type != bundleType {
continue
}
if len(bundleIDs) > 0 && !containsStr(record.Id, bundleIDs) {
continue
}
records = append(records, &record)
}
return records, nil
}
// containsStr checks if the strs slice contains str
func containsStr(str string, strs []string) bool {
for _, s := range strs {
if s == str {
return true
}
}
return false
}
// ReadBundle tries to find a bundle by the given id within the dataPath.
func (s Store) ReadBundle(bundleID string) (*proto.Bundle, error) {
m.RLock()
defer m.RUnlock()
filePath := s.buildFilePathForBundle(bundleID, false)
record := proto.Bundle{}
if err := s.parseRecordFromFile(&record, filePath); err != nil {
return nil, err
}
s.Logger.Debug().Msgf("read contents from file: %v", filePath)
return &record, nil
}
// ReadSetting tries to find a setting by the given id within the dataPath.
func (s Store) ReadSetting(settingID string) (*proto.Setting, error) {
m.RLock()
defer m.RUnlock()
bundles, err := s.ListBundles(proto.Bundle_TYPE_DEFAULT, []string{})
if err != nil {
return nil, err
}
for _, bundle := range bundles {
for _, setting := range bundle.Settings {
if setting.Id == settingID {
return setting, nil
}
}
}
return nil, fmt.Errorf("could not read setting: %v", settingID)
}
// WriteBundle writes the given record into a file within the dataPath.
func (s Store) WriteBundle(record *proto.Bundle) (*proto.Bundle, error) {
// FIXME: locking should happen on the file here, not globally.
m.Lock()
defer m.Unlock()
if record.Id == "" {
record.Id = uuid.Must(uuid.NewV4()).String()
}
filePath := s.buildFilePathForBundle(record.Id, true)
if err := s.writeRecordToFile(record, filePath); err != nil {
return nil, err
}
s.Logger.Debug().Msgf("request contents written to file: %v", filePath)
return record, nil
}
// AddSettingToBundle adds the given setting to the bundle with the given bundleID.
func (s Store) AddSettingToBundle(bundleID string, setting *proto.Setting) (*proto.Setting, error) {
bundle, err := s.ReadBundle(bundleID)
if err != nil {
return nil, err
}
if setting.Id == "" {
setting.Id = uuid.Must(uuid.NewV4()).String()
}
setSetting(bundle, setting)
_, err = s.WriteBundle(bundle)
if err != nil {
return nil, err
}
return setting, nil
}
// RemoveSettingFromBundle removes the setting from the bundle with the given ids.
func (s Store) RemoveSettingFromBundle(bundleID string, settingID string) error {
bundle, err := s.ReadBundle(bundleID)
if err != nil {
return nil
}
if ok := removeSetting(bundle, settingID); ok {
if _, err := s.WriteBundle(bundle); err != nil {
return err
}
}
return nil
}
// indexOfSetting finds the index of the given setting within the given bundle.
// returns -1 if the setting was not found.
func indexOfSetting(bundle *proto.Bundle, settingID string) int {
for index := range bundle.Settings {
s := bundle.Settings[index]
if s.Id == settingID {
return index
}
}
return -1
}
// setSetting will append or overwrite the given setting within the given bundle
func setSetting(bundle *proto.Bundle, setting *proto.Setting) {
m.Lock()
defer m.Unlock()
index := indexOfSetting(bundle, setting.Id)
if index == -1 {
bundle.Settings = append(bundle.Settings, setting)
} else {
bundle.Settings[index] = setting
}
}
// removeSetting will remove the given setting from the given bundle
func removeSetting(bundle *proto.Bundle, settingID string) bool {
m.Lock()
defer m.Unlock()
index := indexOfSetting(bundle, settingID)
if index == -1 {
return false
}
bundle.Settings = append(bundle.Settings[:index], bundle.Settings[index+1:]...)
return true
}

View File

@@ -0,0 +1,155 @@
package store
import (
"testing"
olog "github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
"github.com/stretchr/testify/assert"
)
var bundleScenarios = []struct {
name string
bundle *proto.Bundle
}{
{
name: "generic-test-file-resource",
bundle: &proto.Bundle{
Id: bundle1,
Type: proto.Bundle_TYPE_DEFAULT,
Extension: extension1,
DisplayName: "test1",
Resource: &proto.Resource{
Type: proto.Resource_TYPE_FILE,
Id: "beep",
},
Settings: []*proto.Setting{
{
Id: setting1,
Description: "test-desc-1",
DisplayName: "test-displayname-1",
Resource: &proto.Resource{
Type: proto.Resource_TYPE_FILE,
Id: "bleep",
},
Value: &proto.Setting_IntValue{
IntValue: &proto.Int{
Min: 0,
Max: 42,
},
},
},
},
},
},
{
name: "generic-test-system-resource",
bundle: &proto.Bundle{
Id: bundle2,
Type: proto.Bundle_TYPE_DEFAULT,
Extension: extension2,
DisplayName: "test1",
Resource: &proto.Resource{
Type: proto.Resource_TYPE_SYSTEM,
},
Settings: []*proto.Setting{
{
Id: setting2,
Description: "test-desc-2",
DisplayName: "test-displayname-2",
Resource: &proto.Resource{
Type: proto.Resource_TYPE_SYSTEM,
},
Value: &proto.Setting_IntValue{
IntValue: &proto.Int{
Min: 0,
Max: 42,
},
},
},
},
},
},
{
name: "generic-test-role-bundle",
bundle: &proto.Bundle{
Id: bundle3,
Type: proto.Bundle_TYPE_ROLE,
Extension: extension1,
DisplayName: "Role1",
Resource: &proto.Resource{
Type: proto.Resource_TYPE_SYSTEM,
},
Settings: []*proto.Setting{
{
Id: setting3,
Description: "test-desc-3",
DisplayName: "test-displayname-3",
Resource: &proto.Resource{
Type: proto.Resource_TYPE_SETTING,
Id: setting1,
},
Value: &proto.Setting_PermissionValue{
PermissionValue: &proto.Permission{
Operation: proto.Permission_OPERATION_READ,
Constraint: proto.Permission_CONSTRAINT_OWN,
},
},
},
},
},
},
}
func TestBundles(t *testing.T) {
s := Store{
dataPath: dataRoot,
Logger: olog.NewLogger(
olog.Color(true),
olog.Pretty(true),
olog.Level("info"),
),
}
// write bundles
for i := range bundleScenarios {
index := i
t.Run(bundleScenarios[index].name, func(t *testing.T) {
filePath := s.buildFilePathForBundle(bundleScenarios[index].bundle.Id, true)
if err := s.writeRecordToFile(bundleScenarios[index].bundle, filePath); err != nil {
t.Error(err)
}
assert.FileExists(t, filePath)
})
}
// check that ListBundles only returns bundles with type DEFAULT
bundles, err := s.ListBundles(proto.Bundle_TYPE_DEFAULT, []string{})
if err != nil {
t.Error(err)
}
for i := range bundles {
assert.Equal(t, proto.Bundle_TYPE_DEFAULT, bundles[i].Type)
}
// check that ListBundles filtered by an id only returns that bundle
filteredBundles, err := s.ListBundles(proto.Bundle_TYPE_DEFAULT, []string{bundle2})
if err != nil {
t.Error(err)
}
assert.Equal(t, 1, len(filteredBundles))
if len(filteredBundles) == 1 {
assert.Equal(t, bundle2, filteredBundles[0].Id)
}
// check that ListRoles only returns bundles with type ROLE
roles, err := s.ListBundles(proto.Bundle_TYPE_ROLE, []string{})
if err != nil {
t.Error(err)
}
for i := range roles {
assert.Equal(t, proto.Bundle_TYPE_ROLE, roles[i].Type)
}
burnRoot()
}

View File

@@ -0,0 +1,39 @@
package store
import (
"os"
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
)
// Unmarshal file into record
func (s Store) parseRecordFromFile(record proto.Message, filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
decoder := jsonpb.Unmarshaler{}
if err = decoder.Unmarshal(file, record); err != nil {
return err
}
return nil
}
// Marshal record into file
func (s Store) writeRecordToFile(record proto.Message, filePath string) error {
file, err := os.Create(filePath)
if err != nil {
return err
}
defer file.Close()
encoder := jsonpb.Marshaler{}
if err = encoder.Marshal(file, record); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,65 @@
package store
import (
"os"
"path/filepath"
)
const folderNameBundles = "bundles"
const folderNameValues = "values"
const folderNameAssignments = "assignments"
// buildFolderPathForBundles builds the folder path for storing settings bundles. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFolderPathForBundles(mkdir bool) string {
folderPath := filepath.Join(s.dataPath, folderNameBundles)
if mkdir {
s.ensureFolderExists(folderPath)
}
return folderPath
}
// buildFilePathForBundle builds a unique file name from the given params. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFilePathForBundle(bundleID string, mkdir bool) string {
extensionFolder := s.buildFolderPathForBundles(mkdir)
return filepath.Join(extensionFolder, bundleID+".json")
}
// buildFolderPathForValues builds the folder path for storing settings values. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFolderPathForValues(mkdir bool) string {
folderPath := filepath.Join(s.dataPath, folderNameValues)
if mkdir {
s.ensureFolderExists(folderPath)
}
return folderPath
}
// buildFilePathForValue builds a unique file name from the given params. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFilePathForValue(valueID string, mkdir bool) string {
extensionFolder := s.buildFolderPathForValues(mkdir)
return filepath.Join(extensionFolder, valueID+".json")
}
// buildFolderPathForRoleAssignments builds the folder path for storing role assignments. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFolderPathForRoleAssignments(mkdir bool) string {
roleAssignmentsFolder := filepath.Join(s.dataPath, folderNameAssignments)
if mkdir {
s.ensureFolderExists(roleAssignmentsFolder)
}
return roleAssignmentsFolder
}
// buildFilePathForRoleAssignment builds a unique file name from the given params. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFilePathForRoleAssignment(assignmentID string, mkdir bool) string {
roleAssignmentsFolder := s.buildFolderPathForRoleAssignments(mkdir)
return filepath.Join(roleAssignmentsFolder, assignmentID+".json")
}
// ensureFolderExists checks if the given path is an existing folder and creates one if not existing
func (s Store) ensureFolderExists(path string) {
if _, err := os.Stat(path); os.IsNotExist(err) {
err = os.MkdirAll(path, 0700)
if err != nil {
s.Logger.Err(err).Msgf("Error creating folder %v", path)
}
}
}

View File

@@ -0,0 +1,52 @@
package store
import (
"github.com/owncloud/ocis-settings/pkg/proto/v0"
"github.com/owncloud/ocis-settings/pkg/util"
)
// ListPermissionsByResource collects all permissions from the provided roleIDs that match the requested resource
func (s Store) ListPermissionsByResource(resource *proto.Resource, roleIDs []string) ([]*proto.Permission, error) {
records := make([]*proto.Permission, 0)
for _, roleID := range roleIDs {
role, err := s.ReadBundle(roleID)
if err != nil {
s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping")
continue
}
records = append(records, extractPermissionsByResource(resource, role)...)
}
return records, nil
}
// ReadPermissionByID finds the permission in the roles, specified by the provided roleIDs
func (s Store) ReadPermissionByID(permissionID string, roleIDs []string) (*proto.Permission, error) {
for _, roleID := range roleIDs {
role, err := s.ReadBundle(roleID)
if err != nil {
s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping")
continue
}
for _, permission := range role.Settings {
if permission.Id == permissionID {
if value, ok := permission.Value.(*proto.Setting_PermissionValue); ok {
return value.PermissionValue, nil
}
}
}
}
return nil, nil
}
// extractPermissionsByResource collects all permissions from the provided role that match the requested resource
func extractPermissionsByResource(resource *proto.Resource, role *proto.Bundle) []*proto.Permission {
permissions := make([]*proto.Permission, 0)
for _, setting := range role.Settings {
if value, ok := setting.Value.(*proto.Setting_PermissionValue); ok {
if util.IsResourceMatched(setting.Resource, resource) {
permissions = append(permissions, value.PermissionValue)
}
}
}
return permissions
}

View File

@@ -0,0 +1,49 @@
// Package store implements the go-micro store interface
package store
import (
"os"
olog "github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-settings/pkg/config"
"github.com/owncloud/ocis-settings/pkg/settings"
)
var (
// Name is the default name for the settings store
Name = "ocis-settings"
managerName = "filesystem"
)
// Store interacts with the filesystem to manage settings information
type Store struct {
dataPath string
Logger olog.Logger
}
// New creates a new store
func New(cfg *config.Config) settings.Manager {
s := Store{
Logger: olog.NewLogger(
olog.Color(cfg.Log.Color),
olog.Pretty(cfg.Log.Pretty),
olog.Level(cfg.Log.Level),
),
}
if _, err := os.Stat(cfg.Storage.DataPath); err != nil {
s.Logger.Info().Msgf("creating container on %v", cfg.Storage.DataPath)
err := os.MkdirAll(cfg.Storage.DataPath, 0700)
if err != nil {
s.Logger.Err(err).Msgf("providing container on %v", cfg.Storage.DataPath)
}
}
s.dataPath = cfg.Storage.DataPath
return &s
}
func init() {
settings.Registry[managerName] = New
}

View File

@@ -0,0 +1,39 @@
package store
import (
"os"
"path/filepath"
)
const (
// account UUIDs
accountUUID1 = "c4572da7-6142-4383-8fc6-efde3d463036"
//accountUUID2 = "e11f9769-416a-427d-9441-41a0e51391d7"
//accountUUID3 = "633ecd77-1980-412a-8721-bf598a330bb4"
// extension names
extension1 = "test-extension-1"
extension2 = "test-extension-2"
// bundle ids
bundle1 = "2f06addf-4fd2-49d5-8f71-00fbd3a3ec47"
bundle2 = "2d745744-749c-4286-8e92-74a24d8331c5"
bundle3 = "d8fd27d1-c00b-4794-a658-416b756a72ff"
// setting ids
setting1 = "c7ebbc8b-d15a-4f2e-9d7d-d6a4cf858d1a"
setting2 = "3fd9a3d9-20b7-40d4-9294-b22bb5868c10"
setting3 = "24bb9535-3df4-42f1-a622-7c0562bec99f"
// value ids
value1 = "fd3b6221-dc13-4a22-824d-2480495f1cdb"
value2 = "2a0bd9b0-ca1d-491a-8c56-d2ddfd68ded8"
//value3 = "b42702d2-5e4d-4d73-b133-e1f9e285355e"
dataRoot = "/var/tmp/herecomesthesun"
)
func burnRoot() {
os.RemoveAll(filepath.Join(dataRoot, "values"))
os.RemoveAll(filepath.Join(dataRoot, "bundles"))
}

View File

@@ -0,0 +1,109 @@
// Package store implements the go-micro store interface
package store
import (
"fmt"
"io/ioutil"
"path/filepath"
"github.com/gofrs/uuid"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
)
// ListValues reads all values that match the given bundleId and accountUUID.
// If the bundleId is empty, it's ignored for filtering.
// If the accountUUID is empty, only values with empty accountUUID are returned.
// If the accountUUID is not empty, values with an empty or with a matching accountUUID are returned.
func (s Store) ListValues(bundleID, accountUUID string) ([]*proto.Value, error) {
valuesFolder := s.buildFolderPathForValues(false)
valueFiles, err := ioutil.ReadDir(valuesFolder)
if err != nil {
return []*proto.Value{}, nil
}
records := make([]*proto.Value, 0, len(valueFiles))
for _, valueFile := range valueFiles {
record := proto.Value{}
err := s.parseRecordFromFile(&record, filepath.Join(valuesFolder, valueFile.Name()))
if err != nil {
s.Logger.Warn().Msgf("error reading %v", valueFile)
continue
}
if bundleID != "" && record.BundleId != bundleID {
continue
}
// if requested accountUUID empty -> fetch all system level values
if accountUUID == "" && record.AccountUuid != "" {
continue
}
// if requested accountUUID empty -> fetch all individual + all system level values
if accountUUID != "" && record.AccountUuid != "" && record.AccountUuid != accountUUID {
continue
}
records = append(records, &record)
}
return records, nil
}
// ReadValue tries to find a value by the given valueId within the dataPath
func (s Store) ReadValue(valueID string) (*proto.Value, error) {
filePath := s.buildFilePathForValue(valueID, false)
record := proto.Value{}
if err := s.parseRecordFromFile(&record, filePath); err != nil {
return nil, err
}
s.Logger.Debug().Msgf("read contents from file: %v", filePath)
return &record, nil
}
// ReadValueByUniqueIdentifiers tries to find a value given a set of unique identifiers
func (s Store) ReadValueByUniqueIdentifiers(accountUUID, settingID string) (*proto.Value, error) {
valuesFolder := s.buildFolderPathForValues(false)
files, err := ioutil.ReadDir(valuesFolder)
if err != nil {
return nil, err
}
for i := range files {
if !files[i].IsDir() {
r := proto.Value{}
s.Logger.Debug().Msgf("reading contents from file: %v", filepath.Join(valuesFolder, files[i].Name()))
if err := s.parseRecordFromFile(&r, filepath.Join(valuesFolder, files[i].Name())); err != nil {
s.Logger.Debug().Msgf("match found: %v", filepath.Join(valuesFolder, files[i].Name()))
return &proto.Value{}, nil
}
// if value saved without accountUUID, then it's a global value
if r.AccountUuid == "" && r.SettingId == settingID {
return &r, nil
}
// if value saved with accountUUID, then it's a user specific value
if r.AccountUuid == accountUUID && r.SettingId == settingID {
return &r, nil
}
}
}
return nil, fmt.Errorf("could not read value by settingID=%v and accountID=%v", settingID, accountUUID)
}
// WriteValue writes the given value into a file within the dataPath
func (s Store) WriteValue(value *proto.Value) (*proto.Value, error) {
s.Logger.Debug().Str("value", value.String()).Msg("writing value")
if value.Id == "" {
value.Id = uuid.Must(uuid.NewV4()).String()
}
// modify value depending on associated resource
if value.Resource.Type == proto.Resource_TYPE_SYSTEM {
value.AccountUuid = ""
}
// write the value
filePath := s.buildFilePathForValue(value.Id, true)
if err := s.writeRecordToFile(value, filePath); err != nil {
return nil, err
}
return value, nil
}

View File

@@ -0,0 +1,70 @@
package store
import (
"testing"
olog "github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
"github.com/stretchr/testify/assert"
)
var valueScenarios = []struct {
name string
value *proto.Value
}{
{
name: "generic-test-with-system-resource",
value: &proto.Value{
Id: value1,
BundleId: bundle1,
SettingId: setting1,
AccountUuid: accountUUID1,
Resource: &proto.Resource{
Type: proto.Resource_TYPE_SYSTEM,
},
Value: &proto.Value_StringValue{
StringValue: "lalala",
},
},
},
{
name: "generic-test-with-file-resource",
value: &proto.Value{
Id: value2,
BundleId: bundle1,
SettingId: setting2,
AccountUuid: accountUUID1,
Resource: &proto.Resource{
Type: proto.Resource_TYPE_FILE,
Id: "adfba82d-919a-41c3-9cd1-5a3f83b2bf76",
},
Value: &proto.Value_StringValue{
StringValue: "tralala",
},
},
},
}
func TestValues(t *testing.T) {
s := Store{
dataPath: dataRoot,
Logger: olog.NewLogger(
olog.Color(true),
olog.Pretty(true),
olog.Level("info"),
),
}
for i := range valueScenarios {
index := i
t.Run(valueScenarios[index].name, func(t *testing.T) {
filePath := s.buildFilePathForValue(valueScenarios[index].value.Id, true)
if err := s.writeRecordToFile(valueScenarios[index].value, filePath); err != nil {
t.Error(err)
}
assert.FileExists(t, filePath)
})
}
burnRoot()
}

View File

@@ -0,0 +1,6 @@
package store
import (
// init filesystem store
_ "github.com/owncloud/ocis-settings/pkg/store/filesystem"
)

View File

@@ -0,0 +1,16 @@
package util
import "github.com/owncloud/ocis-settings/pkg/proto/v0"
const (
// ResourceIDAll declares on a resource that it matches any id
ResourceIDAll = "all"
)
// IsResourceMatched checks if the `example` resource is an exact match or a subset of `definition`
func IsResourceMatched(definition, example *proto.Resource) bool {
if definition.Type != example.Type {
return false
}
return definition.Id == ResourceIDAll || definition.Id == example.Id
}

View File

@@ -0,0 +1,91 @@
package util
import (
"testing"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
"gotest.tools/assert"
)
func TestIsResourceMatched(t *testing.T) {
scenarios := []struct {
name string
definition *proto.Resource
example *proto.Resource
matched bool
}{
{
"same resource types without ids match",
&proto.Resource{
Type: proto.Resource_TYPE_SYSTEM,
},
&proto.Resource{
Type: proto.Resource_TYPE_SYSTEM,
},
true,
},
{
"different resource types without ids don't match",
&proto.Resource{
Type: proto.Resource_TYPE_SYSTEM,
},
&proto.Resource{
Type: proto.Resource_TYPE_USER,
},
false,
},
{
"same resource types with different ids don't match",
&proto.Resource{
Type: proto.Resource_TYPE_USER,
Id: "einstein",
},
&proto.Resource{
Type: proto.Resource_TYPE_USER,
Id: "marie",
},
false,
},
{
"same resource types with same ids match",
&proto.Resource{
Type: proto.Resource_TYPE_USER,
Id: "einstein",
},
&proto.Resource{
Type: proto.Resource_TYPE_USER,
Id: "einstein",
},
true,
},
{
"same resource types with definition = ALL and without id in example is a match",
&proto.Resource{
Type: proto.Resource_TYPE_USER,
Id: ResourceIDAll,
},
&proto.Resource{
Type: proto.Resource_TYPE_USER,
},
true,
},
{
"same resource types with definition.id = ALL and with some id in example is a match",
&proto.Resource{
Type: proto.Resource_TYPE_USER,
Id: ResourceIDAll,
},
&proto.Resource{
Type: proto.Resource_TYPE_USER,
Id: "einstein",
},
true,
},
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
assert.Equal(t, scenario.matched, IsResourceMatched(scenario.definition, scenario.example))
})
}
}

View File

@@ -0,0 +1,19 @@
package version
import (
"time"
)
var (
// String gets defined by the build system.
String = "0.0.0"
// Date indicates the build date.
Date = "00000000"
)
// Compiled returns the compile time of this service.
func Compiled() time.Time {
t, _ := time.Parse("20060102", Date)
return t
}