Merge pull request #22 from owncloud/input-validation

Input validation (part 1)
This commit is contained in:
Benedikt Kulmann
2020-06-04 14:24:09 +02:00
committed by GitHub
10 changed files with 376 additions and 187 deletions

View File

@@ -0,0 +1,10 @@
Change: Introduce input validation
We set up input validation, starting with enforcing alphanumeric identifier values and UUID
format on account uuids. As a result, traversal into parent folders is not possible anymore.
We also made sure that get and list requests are side effect free, i.e. not creating any folders.
https://github.com/owncloud/ocis-settings/pull/22
https://github.com/owncloud/ocis-settings/issues/15
https://github.com/owncloud/ocis-settings/issues/16
https://github.com/owncloud/ocis-settings/issues/19

4
go.mod
View File

@@ -7,14 +7,18 @@ require (
contrib.go.opencensus.io/exporter/ocagent v0.6.0
contrib.go.opencensus.io/exporter/zipkin v0.1.1
github.com/UnnoTed/fileb0x v1.1.4
github.com/cespare/reflex v0.2.0 // indirect
github.com/go-chi/chi v4.1.0+incompatible
github.com/go-chi/render v1.0.1
github.com/go-ozzo/ozzo-validation/v4 v4.2.1
github.com/golang/protobuf v1.4.0
github.com/grpc-ecosystem/grpc-gateway v1.14.4
github.com/haya14busa/goverage v0.0.0-20180129164344-eec3514a20b5 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/micro/cli/v2 v2.1.2
github.com/micro/go-micro/v2 v2.6.0
github.com/mitchellh/gox v1.0.1
github.com/ogier/pflag v0.0.1 // indirect
github.com/oklog/run v1.0.0
github.com/openzipkin/zipkin-go v0.2.2
github.com/owncloud/ocis-pkg/v2 v2.2.2-0.20200527082518-5641fa4a4c8c

11
go.sum
View File

@@ -74,6 +74,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0=
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/ascarter/requestid v0.0.0-20170313220838-5b76ab3d4aee h1:3T/l+vMotQ7cDSLWNAn2Vg1SAQ3mdyLgBWWBitSS3uU=
github.com/ascarter/requestid v0.0.0-20170313220838-5b76ab3d4aee/go.mod h1:u7Wtt4WATGGgae9mURNGQQqxAudPKrxfsbSDSGOso+g=
github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
@@ -98,6 +100,8 @@ github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4r
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/reflex v0.2.0 h1:6d9WpWJseKjJvZEevKP7Pk42nPx2+BUTqmhNk8wZPwM=
github.com/cespare/reflex v0.2.0/go.mod h1:ooqOLJ4algvHP/oYvKWfWJ9tFUzCLDk5qkIJduMYrgI=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
@@ -212,6 +216,9 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE=
github.com/go-ozzo/ozzo-validation/v4 v4.2.1 h1:XALUNshPYumA7UShB7iM3ZVlqIBn0jfwjqAMIoyE1N0=
github.com/go-ozzo/ozzo-validation/v4 v4.2.1/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
@@ -346,6 +353,8 @@ github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
github.com/karrick/godirwalk v1.7.8 h1:VfG72pyIxgtC7+3X9CMHI0AOl4LwyRAg98WAgsvffi8=
github.com/karrick/godirwalk v1.7.8/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@@ -484,6 +493,8 @@ github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750=
github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=

View File

@@ -1,5 +1,5 @@
// Code generated by fileb0x at "2020-05-18 11:04:02.579403 +0200 CEST m=+0.032731659" from config file "embed.yml" DO NOT EDIT.
// modification hash(8cc9a42d333f443b4cd19fc245770308.8058aec596c5fb73022d09bb97af796e)
// Code generated by fileb0x at "2020-06-03 13:34:57.567257 +0200 CEST m=+0.040129778" from config file "embed.yml" DO NOT EDIT.
// modification hash(65d24f2e8af3a68b0d8a76c191429e1f.8058aec596c5fb73022d09bb97af796e)
package assets

View File

@@ -16,6 +16,21 @@ import (
var service = grpc.Service{}
var (
dummySettings = []*proto.Setting{
{
DisplayName: "dummy setting",
SettingKey: "dummy-setting",
Value: &proto.Setting_IntValue{
IntValue: &proto.IntSetting{
Default: 42,
},
},
Description: "dummy setting",
},
}
)
func init() {
service = grpc.NewService(
grpc.Namespace("com.owncloud.api"),
@@ -46,7 +61,7 @@ type CustomError struct {
testing that saving a settings bundle and retrieving it again works correctly
using various setting bundle properties
*/
func TestSaveGetSettingsBundleWithNoSettings(t *testing.T) {
func TestSettingsBundleProperties(t *testing.T) {
type TestStruct struct {
testDataName string
BundleKey string
@@ -68,15 +83,28 @@ func TestSaveGetSettingsBundleWithNoSettings(t *testing.T) {
CustomError{},
},
{
"UTF",
"UTF disallowed on keys",
"सिम्प्ले-bundle-key",
"सिम्प्ले-key",
"सिम्प्ले-display-name",
"सिम्प्ले-extension-name",
"सिम्प्ले",
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "bundle_key: must be in a valid format; extension: must be in a valid format.",
Status: "Internal Server Error",
},
},
{
"UTF allowed on display name",
"simple-bundle-key",
"simple-key",
"सिम्प्ले-display-name",
"simple-extension-name",
"123e4567-e89b-12d3-a456-426652340000",
CustomError{},
},
//https://github.com/owncloud/ocis-settings/issues/15
{
"bundle key with ../ in the name",
"../file-a-level-higher-up",
@@ -84,9 +112,13 @@ func TestSaveGetSettingsBundleWithNoSettings(t *testing.T) {
"simple-display-name",
"simple-extension-name",
"123e4567-e89b-12d3-a456-426652340000",
CustomError{},
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "bundle_key: must be in a valid format.",
Status: "Internal Server Error",
},
},
//https://github.com/owncloud/ocis-settings/issues/15
{
"bundle key in the root directory",
"/tmp/file",
@@ -97,11 +129,10 @@ func TestSaveGetSettingsBundleWithNoSettings(t *testing.T) {
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "open ocis-settings-store/bundles/simple-extension-name/tmp/file.json: no such file or directory",
Detail: "bundle_key: must be in a valid format.",
Status: "Internal Server Error",
},
},
//https://github.com/owncloud/ocis-settings/issues/15
{
"extension name with ../ in the name",
"simple-bundle-key",
@@ -109,9 +140,13 @@ func TestSaveGetSettingsBundleWithNoSettings(t *testing.T) {
"simple-display-name",
"../folder-a-level-higher-up",
"123e4567-e89b-12d3-a456-426652340000",
CustomError{},
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "extension: must be in a valid format.",
Status: "Internal Server Error",
},
},
//https://github.com/owncloud/ocis-settings/issues/16
{
"extension name with \\ in the name",
"simple-bundle-key",
@@ -119,9 +154,13 @@ func TestSaveGetSettingsBundleWithNoSettings(t *testing.T) {
"simple-display-name",
"\\",
"123e4567-e89b-12d3-a456-426652340000",
CustomError{},
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "extension: must be in a valid format.",
Status: "Internal Server Error",
},
},
//https://github.com/owncloud/ocis-settings/issues/16
{
"bundle key with \\ as the name",
"\\",
@@ -129,13 +168,32 @@ func TestSaveGetSettingsBundleWithNoSettings(t *testing.T) {
"simple-display-name",
"simple-extension-name",
"123e4567-e89b-12d3-a456-426652340000",
CustomError{},
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "bundle_key: must be in a valid format.",
Status: "Internal Server Error",
},
},
{
"spaces in values",
"spaces are disallowed in keys",
"simple bundle key",
"simple key",
"simple display name",
"simple extension name",
"123e4567-e89b-12d3-a456-426652340000",
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "bundle_key: must be in a valid format; extension: must be in a valid format.",
Status: "Internal Server Error",
},
},
{
"spaces are allowed in display names",
"simple-bundle-key",
"simple-key",
"simple-display-name",
"simple display name",
"simple-extension-name",
"123e4567-e89b-12d3-a456-426652340000",
CustomError{},
@@ -150,7 +208,7 @@ func TestSaveGetSettingsBundleWithNoSettings(t *testing.T) {
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "rpc error: code = InvalidArgument desc = Missing a required identifier attribute",
Detail: "bundle_key: cannot be blank.",
Status: "Internal Server Error",
},
},
@@ -164,12 +222,12 @@ func TestSaveGetSettingsBundleWithNoSettings(t *testing.T) {
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "rpc error: code = InvalidArgument desc = Missing a required identifier attribute",
Detail: "extension: cannot be blank.",
Status: "Internal Server Error",
},
},
{
"setting key missing",
"setting key missing (omitted on bundles)",
"simple-bundle-key",
"",
"simple-display-name",
@@ -184,10 +242,15 @@ func TestSaveGetSettingsBundleWithNoSettings(t *testing.T) {
"",
"simple-extension-name",
"123e4567-e89b-12d3-a456-426652340000",
CustomError{},
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "display_name: cannot be blank.",
Status: "Internal Server Error",
},
},
{
"UUID missing",
"UUID missing (omitted on bundles)",
"simple-bundle-key",
"simple-key",
"simple-display-name",
@@ -208,7 +271,7 @@ func TestSaveGetSettingsBundleWithNoSettings(t *testing.T) {
bundle := proto.SettingsBundle{
Identifier: &identifier,
DisplayName: testCase.DisplayName,
Settings: nil,
Settings: dummySettings,
}
createRequest := proto.SaveSettingsBundleRequest{
SettingsBundle: &bundle,
@@ -219,6 +282,7 @@ func TestSaveGetSettingsBundleWithNoSettings(t *testing.T) {
cresponse, err := cl.SaveSettingsBundle(context.Background(), &createRequest)
if err != nil || (CustomError{} != testCase.expectedError) {
assert.Error(t, err)
var errorData CustomError
_ = json.Unmarshal([]byte(err.Error()), &errorData)
assert.Equal(t, testCase.expectedError.ID, errorData.ID)
@@ -246,6 +310,31 @@ func TestSaveGetSettingsBundleWithNoSettings(t *testing.T) {
}
}
func TestSettingsBundleWithoutSettings(t *testing.T) {
client := service.Client()
cl := proto.NewBundleService("com.owncloud.api.settings", client)
createRequest := proto.SaveSettingsBundleRequest{
SettingsBundle: &proto.SettingsBundle{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "alices-bundle",
},
DisplayName: "Alice's Bundle",
},
}
response, err := cl.SaveSettingsBundle(context.Background(), &createRequest)
assert.Error(t, err)
assert.Nil(t, response)
var errorData CustomError
_ = json.Unmarshal([]byte(err.Error()), &errorData)
assert.Equal(t, "go.micro.client", errorData.ID)
assert.Equal(t, 500, errorData.Code)
assert.Equal(t, "settings: cannot be blank.", errorData.Detail)
assert.Equal(t, "Internal Server Error", errorData.Status)
_ = os.RemoveAll("ocis-settings-store")
}
/**
testing that setting getting and listing a settings bundle works correctly with a set of setting definitions
*/
@@ -565,12 +654,11 @@ func TestGetSettingsBundleCreatesFolder(t *testing.T) {
getRequest := proto.GetSettingsBundleRequest{Identifier: &identifier}
_, _ = cl.GetSettingsBundle(context.Background(), &getRequest)
assert.DirExists(t, "ocis-settings-store/bundles/not-existing-extension")
assert.NoDirExists(t, "ocis-settings-store/bundles/not-existing-extension")
assert.NoFileExists(t, "ocis-settings-store/bundles/not-existing-extension/not-existing-bundle.json")
_ = os.RemoveAll("ocis-settings-store")
}
//https://github.com/owncloud/ocis-settings/issues/15
func TestGetSettingsBundleAccessOtherBundle(t *testing.T) {
client := service.Client()
cl := proto.NewBundleService("com.owncloud.api.settings", client)
@@ -581,7 +669,7 @@ func TestGetSettingsBundleAccessOtherBundle(t *testing.T) {
BundleKey: "alice-bundle",
},
DisplayName: "alice settings bundle",
Settings: nil,
Settings: dummySettings,
}
createRequest := proto.SaveSettingsBundleRequest{
SettingsBundle: &aliceBundle,
@@ -597,9 +685,14 @@ func TestGetSettingsBundleAccessOtherBundle(t *testing.T) {
getRequest := proto.GetSettingsBundleRequest{Identifier: &bobIdentifier}
response, err := cl.GetSettingsBundle(context.Background(), &getRequest)
assert.NoError(t, err)
assert.Equal(t, response.SettingsBundle.Identifier.Extension, "alice-extension")
assert.Equal(t, response.SettingsBundle.Identifier.BundleKey, "alice-bundle")
assert.Error(t, err)
assert.Nil(t, response)
var errorData CustomError
_ = json.Unmarshal([]byte(err.Error()), &errorData)
assert.Equal(t, "go.micro.client", errorData.ID)
assert.Equal(t, 500, errorData.Code)
assert.Equal(t, "extension: must be in a valid format.", errorData.Detail)
assert.Equal(t, "Internal Server Error", errorData.Status)
_ = os.RemoveAll("ocis-settings-store")
}
@@ -619,18 +712,17 @@ func TestGetSettingsBundleWithInvalidIdentifier(t *testing.T) {
var tests = []TestStruct{
{
"not existing",
"this key should not exist",
"this key should not exist",
"this.extension.should.not.exist",
"this-key-should-not-exist",
"this-key-should-not-exist",
"this-extension-should-not-exist",
"123e4567-e89b-12d3-a456-426652340000",
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "open ocis-settings-store/bundles/this.extension.should.not.exist/this key should not exist.json: no such file or directory",
Detail: "open ocis-settings-store/bundles/this-extension-should-not-exist/this-key-should-not-exist.json: no such file or directory",
Status: "Internal Server Error",
},
},
//https://github.com/owncloud/ocis-settings/issues/15
{
"bundle key in the root directory",
"/tmp/file",
@@ -640,7 +732,7 @@ func TestGetSettingsBundleWithInvalidIdentifier(t *testing.T) {
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "open ocis-settings-store/bundles/simple-extension-name/tmp/file.json: no such file or directory",
Detail: "bundle_key: must be in a valid format.",
Status: "Internal Server Error",
},
},
@@ -653,7 +745,7 @@ func TestGetSettingsBundleWithInvalidIdentifier(t *testing.T) {
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "rpc error: code = InvalidArgument desc = Missing a required identifier attribute",
Detail: "bundle_key: cannot be blank.",
Status: "Internal Server Error",
},
},
@@ -666,7 +758,7 @@ func TestGetSettingsBundleWithInvalidIdentifier(t *testing.T) {
CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "rpc error: code = InvalidArgument desc = Missing a required identifier attribute",
Detail: "extension: cannot be blank.",
Status: "Internal Server Error",
},
},
@@ -688,6 +780,7 @@ func TestGetSettingsBundleWithInvalidIdentifier(t *testing.T) {
getResponse, err := cl.GetSettingsBundle(context.Background(), &getRequest)
if err != nil || (CustomError{} != testCase.expectedError) {
var errorData CustomError
assert.Error(t, err)
assert.Empty(t, getResponse)
_ = json.Unmarshal([]byte(err.Error()), &errorData)
assert.Equal(t, testCase.expectedError.ID, errorData.ID)
@@ -710,8 +803,10 @@ func TestListMultipleSettingsBundlesOfSameExtension(t *testing.T) {
SettingsBundle: &proto.SettingsBundle{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "alice's-bundle",
BundleKey: "alices-bundle",
},
DisplayName: "Alice's Bundle",
Settings: dummySettings,
},
}
_, err := cl.SaveSettingsBundle(context.Background(), &createRequest)
@@ -721,8 +816,10 @@ func TestListMultipleSettingsBundlesOfSameExtension(t *testing.T) {
SettingsBundle: &proto.SettingsBundle{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "bob's-bundle",
BundleKey: "bobs-bundle",
},
DisplayName: "Bob's Bundle",
Settings: dummySettings,
},
}
_, err = cl.SaveSettingsBundle(context.Background(), &createRequest)
@@ -732,8 +829,10 @@ func TestListMultipleSettingsBundlesOfSameExtension(t *testing.T) {
SettingsBundle: &proto.SettingsBundle{
Identifier: &proto.Identifier{
Extension: "an-other-extension",
BundleKey: "bob's-bundle",
BundleKey: "bobs-bundle",
},
DisplayName: "Bob's Bundle",
Settings: dummySettings,
},
}
_, err = cl.SaveSettingsBundle(context.Background(), &createRequest)
@@ -744,10 +843,10 @@ func TestListMultipleSettingsBundlesOfSameExtension(t *testing.T) {
response, err := cl.ListSettingsBundles(context.Background(), &listRequest)
assert.NoError(t, err)
assert.Equal(t, response.SettingsBundles[0].Identifier.Extension, "great-extension")
assert.Equal(t, response.SettingsBundles[0].Identifier.BundleKey, "alice's-bundle")
assert.Equal(t, response.SettingsBundles[0].Identifier.BundleKey, "alices-bundle")
assert.Equal(t, response.SettingsBundles[1].Identifier.Extension, "great-extension")
assert.Equal(t, response.SettingsBundles[1].Identifier.BundleKey, "bob's-bundle")
assert.Equal(t, response.SettingsBundles[1].Identifier.BundleKey, "bobs-bundle")
assert.Equal(t, 2, len(response.SettingsBundles))
_ = os.RemoveAll("ocis-settings-store")
}
@@ -760,8 +859,10 @@ func TestListAllSettingsBundlesOfSameExtension(t *testing.T) {
SettingsBundle: &proto.SettingsBundle{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "alice's-bundle",
BundleKey: "alices-bundle",
},
DisplayName: "Alice's Bundle",
Settings: dummySettings,
},
}
_, err := cl.SaveSettingsBundle(context.Background(), &createRequest)
@@ -771,8 +872,10 @@ func TestListAllSettingsBundlesOfSameExtension(t *testing.T) {
SettingsBundle: &proto.SettingsBundle{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "bob's-bundle",
BundleKey: "bobs-bundle",
},
DisplayName: "Bob's Bundle",
Settings: dummySettings,
},
}
_, err = cl.SaveSettingsBundle(context.Background(), &createRequest)
@@ -782,8 +885,10 @@ func TestListAllSettingsBundlesOfSameExtension(t *testing.T) {
SettingsBundle: &proto.SettingsBundle{
Identifier: &proto.Identifier{
Extension: "an-other-extension",
BundleKey: "bob's-bundle",
BundleKey: "bobs-bundle",
},
DisplayName: "Bob's Bundle",
Settings: dummySettings,
},
}
_, err = cl.SaveSettingsBundle(context.Background(), &createRequest)
@@ -794,13 +899,13 @@ func TestListAllSettingsBundlesOfSameExtension(t *testing.T) {
response, err := cl.ListSettingsBundles(context.Background(), &listRequest)
assert.NoError(t, err)
assert.Equal(t, response.SettingsBundles[0].Identifier.Extension, "an-other-extension")
assert.Equal(t, response.SettingsBundles[0].Identifier.BundleKey, "bob's-bundle")
assert.Equal(t, response.SettingsBundles[0].Identifier.BundleKey, "bobs-bundle")
assert.Equal(t, response.SettingsBundles[1].Identifier.Extension, "great-extension")
assert.Equal(t, response.SettingsBundles[1].Identifier.BundleKey, "alice's-bundle")
assert.Equal(t, response.SettingsBundles[1].Identifier.BundleKey, "alices-bundle")
assert.Equal(t, response.SettingsBundles[2].Identifier.Extension, "great-extension")
assert.Equal(t, response.SettingsBundles[2].Identifier.BundleKey, "bob's-bundle")
assert.Equal(t, response.SettingsBundles[2].Identifier.BundleKey, "bobs-bundle")
assert.Equal(t, 3, len(response.SettingsBundles))
_ = os.RemoveAll("ocis-settings-store")
}
@@ -814,7 +919,7 @@ func TestListSettingsBundlesOfNonExistingExtension(t *testing.T) {
response, err := cl.ListSettingsBundles(context.Background(), &listRequest)
assert.NoError(t, err)
assert.Empty(t, response.String())
assert.DirExists(t, "ocis-settings-store/bundles")
assert.NoDirExists(t, "ocis-settings-store/bundles")
assert.NoDirExists(t, "ocis-settings-store/bundles/does-not-exist")
}
@@ -825,8 +930,10 @@ func TestListSettingsBundlesInFoldersThatAreNotAccessible(t *testing.T) {
SettingsBundle: &proto.SettingsBundle{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "alice's-bundle",
BundleKey: "alices-bundle",
},
DisplayName: "Alice's Bundle",
Settings: dummySettings,
},
}
_, err := cl.SaveSettingsBundle(context.Background(), &createRequest)
@@ -836,8 +943,10 @@ func TestListSettingsBundlesInFoldersThatAreNotAccessible(t *testing.T) {
SettingsBundle: &proto.SettingsBundle{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "bob's-bundle",
BundleKey: "bobs-bundle",
},
DisplayName: "Bob's Bundle",
Settings: dummySettings,
},
}
_, err = cl.SaveSettingsBundle(context.Background(), &createRequest)
@@ -847,8 +956,10 @@ func TestListSettingsBundlesInFoldersThatAreNotAccessible(t *testing.T) {
SettingsBundle: &proto.SettingsBundle{
Identifier: &proto.Identifier{
Extension: "an-other-extension",
BundleKey: "bob's-bundle",
BundleKey: "bobs-bundle",
},
DisplayName: "Bob's Bundle",
Settings: dummySettings,
},
}
_, err = cl.SaveSettingsBundle(context.Background(), &createRequest)
@@ -857,8 +968,14 @@ func TestListSettingsBundlesInFoldersThatAreNotAccessible(t *testing.T) {
listRequest := proto.ListSettingsBundlesRequest{Identifier: &proto.Identifier{Extension: "../"}}
response, err := cl.ListSettingsBundles(context.Background(), &listRequest)
assert.NoError(t, err)
assert.Empty(t, response.String())
assert.Error(t, err)
assert.Nil(t, response)
var errorData CustomError
_ = json.Unmarshal([]byte(err.Error()), &errorData)
assert.Equal(t, "go.micro.client", errorData.ID)
assert.Equal(t, 500, errorData.Code)
assert.Equal(t, "extension: must be in a valid format.", errorData.Detail)
assert.Equal(t, "Internal Server Error", errorData.Status)
_ = os.RemoveAll("ocis-settings-store")
}
@@ -875,7 +992,7 @@ func TestSaveGetListSettingsValues(t *testing.T) {
SettingsValue: proto.SettingsValue{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "alice's-bundle",
BundleKey: "alices-bundle",
AccountUuid: "123e4567-e89b-12d3-a456-426652340000",
SettingKey: "age",
},
@@ -887,7 +1004,7 @@ func TestSaveGetListSettingsValues(t *testing.T) {
SettingsValue: proto.SettingsValue{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "alice's-bundle",
BundleKey: "alices-bundle",
AccountUuid: "123e4567-e89b-12d3-a456-426652340000",
SettingKey: "location",
},
@@ -899,7 +1016,7 @@ func TestSaveGetListSettingsValues(t *testing.T) {
SettingsValue: proto.SettingsValue{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "alice's-bundle",
BundleKey: "alices-bundle",
AccountUuid: "123e4567-e89b-12d3-a456-426652340000",
SettingKey: "locked",
},
@@ -911,7 +1028,7 @@ func TestSaveGetListSettingsValues(t *testing.T) {
SettingsValue: proto.SettingsValue{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "alice's-bundle",
BundleKey: "alices-bundle",
AccountUuid: "123e4567-e89b-12d3-a456-426652340000",
SettingKey: "currencies",
},
@@ -931,7 +1048,7 @@ func TestSaveGetListSettingsValues(t *testing.T) {
SettingsValue: proto.SettingsValue{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "alice's-bundle",
BundleKey: "alices-bundle",
AccountUuid: "123e4567-e89b-12d3-a456-426652340000",
SettingKey: "font-size",
},
@@ -952,9 +1069,9 @@ func TestSaveGetListSettingsValues(t *testing.T) {
SettingsValue: proto.SettingsValue{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "alice's-bundle",
BundleKey: "alices-bundle",
AccountUuid: "123e4567-e89b-12d3-a456-426652340000",
SettingKey: "apple and peaches",
SettingKey: "apple-and-peaches",
},
Value: &proto.SettingsValue_ListValue{
ListValue: &proto.ListValue{
@@ -971,7 +1088,7 @@ func TestSaveGetListSettingsValues(t *testing.T) {
SettingsValue: proto.SettingsValue{
Identifier: &proto.Identifier{
Extension: "",
BundleKey: "alice's-bundle",
BundleKey: "alices-bundle",
AccountUuid: "123e4567-e89b-12d3-a456-426652340000",
SettingKey: "locked",
},
@@ -980,7 +1097,7 @@ func TestSaveGetListSettingsValues(t *testing.T) {
expectedError: CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "rpc error: code = InvalidArgument desc = Missing a required identifier attribute",
Detail: "extension: cannot be blank.",
Status: "Internal Server Error",
},
},
@@ -998,7 +1115,7 @@ func TestSaveGetListSettingsValues(t *testing.T) {
expectedError: CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "rpc error: code = InvalidArgument desc = Missing a required identifier attribute",
Detail: "bundle_key: cannot be blank.",
Status: "Internal Server Error",
},
},
@@ -1007,7 +1124,7 @@ func TestSaveGetListSettingsValues(t *testing.T) {
SettingsValue: proto.SettingsValue{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "bob's bundle",
BundleKey: "bobs-bundle",
AccountUuid: "",
SettingKey: "locked",
},
@@ -1016,7 +1133,7 @@ func TestSaveGetListSettingsValues(t *testing.T) {
expectedError: CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "rpc error: code = InvalidArgument desc = Missing a required identifier attribute",
Detail: "account_uuid: cannot be blank.",
Status: "Internal Server Error",
},
},
@@ -1025,7 +1142,7 @@ func TestSaveGetListSettingsValues(t *testing.T) {
SettingsValue: proto.SettingsValue{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "bob's bundle",
BundleKey: "bobs-bundle",
AccountUuid: "123e4567-e89b-12d3-a456-426652340000",
SettingKey: "",
},
@@ -1034,48 +1151,63 @@ func TestSaveGetListSettingsValues(t *testing.T) {
expectedError: CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "rpc error: code = InvalidArgument desc = Missing a required identifier attribute",
Detail: "setting_key: cannot be blank.",
Status: "Internal Server Error",
},
},
{
//https://github.com/owncloud/ocis-settings/issues/15
testDataName: "../ in bundle key",
SettingsValue: proto.SettingsValue{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "../bob's bundle",
BundleKey: "../bobs-bundle",
AccountUuid: "123e4567-e89b-12d3-a456-426652340000",
SettingKey: "should-not-be-possible",
},
Value: &proto.SettingsValue_BoolValue{BoolValue: false},
},
expectedError: CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "bundle_key: must be in a valid format.",
Status: "Internal Server Error",
},
},
{
//https://github.com/owncloud/ocis-settings/issues/15
testDataName: "../ in account uuid",
SettingsValue: proto.SettingsValue{
Identifier: &proto.Identifier{
Extension: "great-extension",
BundleKey: "bob's bundle",
BundleKey: "bobs-bundle",
AccountUuid: "../123e4567-e89b-12d3-a456-426652340000",
SettingKey: "should-not-be-possible",
},
Value: &proto.SettingsValue_BoolValue{BoolValue: false},
},
expectedError: CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "account_uuid: must be a valid UUID.",
Status: "Internal Server Error",
},
},
{
//https://github.com/owncloud/ocis-settings/issues/16
testDataName: "\\ in fields that are used to create folder and file names",
SettingsValue: proto.SettingsValue{
Identifier: &proto.Identifier{
Extension: "\\-extension",
BundleKey: "\\ bundle",
BundleKey: "\\-bundle",
AccountUuid: "\\123e4567-e89b-12d3-a456-426652340000",
SettingKey: "should-not-be-possible",
},
Value: &proto.SettingsValue_BoolValue{BoolValue: false},
},
expectedError: CustomError{
ID: "go.micro.client",
Code: 500,
Detail: "account_uuid: must be a valid UUID; bundle_key: must be in a valid format; extension: must be in a valid format.",
Status: "Internal Server Error",
},
},
}
client := service.Client()
@@ -1089,6 +1221,7 @@ func TestSaveGetListSettingsValues(t *testing.T) {
}
saveResponse, err := cl.SaveSettingsValue(context.Background(), &createRequest)
if err != nil || (CustomError{} != tests[index].expectedError) {
assert.Error(t, err)
var errorData CustomError
_ = json.Unmarshal([]byte(err.Error()), &errorData)
assert.Equal(t, tests[index].expectedError.ID, errorData.ID)
@@ -1123,53 +1256,3 @@ func TestSaveGetListSettingsValues(t *testing.T) {
})
}
}
//same as test above but List returns an empty array in case the extension name contains a `../`
//https://github.com/owncloud/ocis-settings/issues/15
func TestListSettingsValuesWithDotsInEntensionName(t *testing.T) {
type TestStruct struct {
testDataName string
SettingsValue proto.SettingsValue
}
var test = TestStruct{
testDataName: "../ in extension name",
SettingsValue: proto.SettingsValue{
Identifier: &proto.Identifier{
Extension: "../great-extension",
BundleKey: "bob's bundle",
AccountUuid: "123e4567-e89b-12d3-a456-426652340000",
SettingKey: "should-not-be-possible",
},
Value: &proto.SettingsValue_BoolValue{BoolValue: false},
},
}
client := service.Client()
cl := proto.NewValueService("com.owncloud.api.settings", client)
expectedSetting, _ := json.Marshal(&test.SettingsValue)
createRequest := proto.SaveSettingsValueRequest{
SettingsValue: &test.SettingsValue,
}
saveResponse, err := cl.SaveSettingsValue(context.Background(), &createRequest)
assert.NoError(t, err)
receivedSetting, _ := json.Marshal(saveResponse.SettingsValue)
assert.Equal(t, expectedSetting, receivedSetting)
getRequest := proto.GetSettingsValueRequest{
Identifier: test.SettingsValue.Identifier,
}
getResponse, err := cl.GetSettingsValue(context.Background(), &getRequest)
assert.NoError(t, err)
receivedSetting, _ = json.Marshal(getResponse.SettingsValue)
assert.Equal(t, expectedSetting, receivedSetting)
listRequest := proto.ListSettingsValuesRequest{
Identifier: test.SettingsValue.Identifier,
}
listResponse, err := cl.ListSettingsValues(context.Background(), &listRequest)
assert.NoError(t, err)
assert.Equal(t, 0, len(listResponse.SettingsValues))
_ = os.RemoveAll("ocis-settings-store")
}

View File

@@ -2,6 +2,7 @@ package svc
import (
"context"
"strings"
"github.com/owncloud/ocis-pkg/v2/middleware"
"github.com/owncloud/ocis-settings/pkg/config"
@@ -27,6 +28,9 @@ func NewService(cfg *config.Config) Service {
// SaveSettingsBundle implements the BundleServiceHandler interface
func (g Service) SaveSettingsBundle(c context.Context, req *proto.SaveSettingsBundleRequest, res *proto.SaveSettingsBundleResponse) error {
req.SettingsBundle.Identifier = getFailsafeIdentifier(c, req.SettingsBundle.Identifier)
if validationError := validateSaveSettingsBundle(req); validationError != nil {
return validationError
}
r, err := g.manager.WriteBundle(req.SettingsBundle)
if err != nil {
return err
@@ -37,7 +41,11 @@ func (g Service) SaveSettingsBundle(c context.Context, req *proto.SaveSettingsBu
// GetSettingsBundle implements the BundleServiceHandler interface
func (g Service) GetSettingsBundle(c context.Context, req *proto.GetSettingsBundleRequest, res *proto.GetSettingsBundleResponse) error {
r, err := g.manager.ReadBundle(getFailsafeIdentifier(c, req.Identifier))
req.Identifier = getFailsafeIdentifier(c, req.Identifier)
if validationError := validateGetSettingsBundle(req); validationError != nil {
return validationError
}
r, err := g.manager.ReadBundle(req.Identifier)
if err != nil {
return err
}
@@ -47,7 +55,11 @@ func (g Service) GetSettingsBundle(c context.Context, req *proto.GetSettingsBund
// ListSettingsBundles implements the BundleServiceHandler interface
func (g Service) ListSettingsBundles(c context.Context, req *proto.ListSettingsBundlesRequest, res *proto.ListSettingsBundlesResponse) error {
r, err := g.manager.ListBundles(getFailsafeIdentifier(c, req.Identifier))
req.Identifier = getFailsafeIdentifier(c, req.Identifier)
if validationError := validateListSettingsBundles(req); validationError != nil {
return validationError
}
r, err := g.manager.ListBundles(req.Identifier)
if err != nil {
return err
}
@@ -58,6 +70,9 @@ func (g Service) ListSettingsBundles(c context.Context, req *proto.ListSettingsB
// SaveSettingsValue implements the ValueServiceHandler interface
func (g Service) SaveSettingsValue(c context.Context, req *proto.SaveSettingsValueRequest, res *proto.SaveSettingsValueResponse) error {
req.SettingsValue.Identifier = getFailsafeIdentifier(c, req.SettingsValue.Identifier)
if validationError := validateSaveSettingsValue(req); validationError != nil {
return validationError
}
r, err := g.manager.WriteValue(req.SettingsValue)
if err != nil {
return err
@@ -68,7 +83,11 @@ func (g Service) SaveSettingsValue(c context.Context, req *proto.SaveSettingsVal
// GetSettingsValue implements the ValueServiceHandler interface
func (g Service) GetSettingsValue(c context.Context, req *proto.GetSettingsValueRequest, res *proto.GetSettingsValueResponse) error {
r, err := g.manager.ReadValue(getFailsafeIdentifier(c, req.Identifier))
req.Identifier = getFailsafeIdentifier(c, req.Identifier)
if validationError := validateGetSettingsValue(req); validationError != nil {
return validationError
}
r, err := g.manager.ReadValue(req.Identifier)
if err != nil {
return err
}
@@ -78,7 +97,11 @@ func (g Service) GetSettingsValue(c context.Context, req *proto.GetSettingsValue
// ListSettingsValues implements the ValueServiceHandler interface
func (g Service) ListSettingsValues(c context.Context, req *proto.ListSettingsValuesRequest, res *proto.ListSettingsValuesResponse) error {
r, err := g.manager.ListValues(getFailsafeIdentifier(c, req.Identifier))
req.Identifier = getFailsafeIdentifier(c, req.Identifier)
if validationError := validateListSettingsValues(req); validationError != nil {
return validationError
}
r, err := g.manager.ListValues(req.Identifier)
if err != nil {
return err
}
@@ -101,5 +124,6 @@ func getFailsafeIdentifier(c context.Context, identifier *proto.Identifier) *pro
identifier.AccountUuid = ""
}
}
identifier.AccountUuid = strings.ToLower(identifier.AccountUuid)
return identifier
}

View File

@@ -0,0 +1,87 @@
package svc
import (
"fmt"
"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 (
regexForKeys = regexp.MustCompile(`^[A-Za-z0-9\-_]*$`)
keyRule = []validation.Rule{
validation.Required,
validation.Match(regexForKeys),
}
settingKeyRule = []validation.Rule{
validation.Required,
validation.Match(regexForKeys),
}
accountUUIDRule = []validation.Rule{
validation.Required,
is.UUID,
}
)
func validateSaveSettingsBundle(req *proto.SaveSettingsBundleRequest) error {
if err := validateBundleIdentifier(req.SettingsBundle.Identifier); err != nil {
return err
}
return validation.ValidateStruct(
req.SettingsBundle,
validation.Field(&req.SettingsBundle.DisplayName, validation.Required),
validation.Field(&req.SettingsBundle.Settings, validation.Required),
)
}
func validateGetSettingsBundle(req *proto.GetSettingsBundleRequest) error {
return validateBundleIdentifier(req.Identifier)
}
func validateListSettingsBundles(req *proto.ListSettingsBundlesRequest) error {
return validation.ValidateStruct(
req.Identifier,
validation.Field(&req.Identifier.Extension, validation.Match(regexForKeys)),
)
}
func validateSaveSettingsValue(req *proto.SaveSettingsValueRequest) error {
return validateValueIdentifier(req.SettingsValue.Identifier)
}
func validateGetSettingsValue(req *proto.GetSettingsValueRequest) error {
return validateValueIdentifier(req.Identifier)
}
func validateListSettingsValues(req *proto.ListSettingsValuesRequest) error {
fmt.Println(req.Identifier)
return validation.ValidateStruct(
req.Identifier,
validation.Field(&req.Identifier.AccountUuid, is.UUID),
validation.Field(&req.Identifier.Extension, validation.Match(regexForKeys)),
validation.Field(&req.Identifier.Extension, validation.When(req.Identifier.BundleKey != "", validation.Required)),
validation.Field(&req.Identifier.BundleKey, validation.Match(regexForKeys)),
validation.Field(&req.Identifier.BundleKey, validation.When(req.Identifier.SettingKey != "", validation.Required)),
validation.Field(&req.Identifier.SettingKey, validation.Match(regexForKeys)),
)
}
func validateBundleIdentifier(identifier *proto.Identifier) error {
return validation.ValidateStruct(
identifier,
validation.Field(&identifier.Extension, keyRule...),
validation.Field(&identifier.BundleKey, keyRule...),
)
}
func validateValueIdentifier(identifier *proto.Identifier) error {
return validation.ValidateStruct(
identifier,
validation.Field(&identifier.Extension, keyRule...),
validation.Field(&identifier.BundleKey, keyRule...),
validation.Field(&identifier.SettingKey, settingKeyRule...),
validation.Field(&identifier.AccountUuid, accountUUIDRule...),
)
}

View File

@@ -6,17 +6,15 @@ import (
"path"
"github.com/owncloud/ocis-settings/pkg/proto/v0"
"google.golang.org/grpc/codes"
gstatus "google.golang.org/grpc/status"
)
// ListBundles returns all bundles in the mountPath folder belonging to the given extension
func (s Store) ListBundles(identifier *proto.Identifier) ([]*proto.SettingsBundle, error) {
bundlesFolder := s.buildFolderPathBundles()
var records []*proto.SettingsBundle
bundlesFolder := s.buildFolderPathBundles(false)
extensionFolders, err := ioutil.ReadDir(bundlesFolder)
if err != nil {
s.Logger.Err(err).Msgf("error reading %v", bundlesFolder)
return nil, err
return records, nil
}
if len(identifier.Extension) < 1 {
@@ -24,7 +22,6 @@ func (s Store) ListBundles(identifier *proto.Identifier) ([]*proto.SettingsBundl
} else {
s.Logger.Info().Msgf("listing bundles by extension %v", identifier.Extension)
}
var records []*proto.SettingsBundle
for _, extensionFolder := range extensionFolders {
extensionPath := path.Join(bundlesFolder, extensionFolder.Name())
bundleFiles, err := ioutil.ReadDir(extensionPath)
@@ -52,12 +49,7 @@ func (s Store) ListBundles(identifier *proto.Identifier) ([]*proto.SettingsBundl
// ReadBundle tries to find a bundle by the given identifier within the mountPath.
// Extension and BundleKey within the identifier are required.
func (s Store) ReadBundle(identifier *proto.Identifier) (*proto.SettingsBundle, error) {
if len(identifier.Extension) < 1 || len(identifier.BundleKey) < 1 {
s.Logger.Error().Msg("extension and bundleKey cannot be empty")
return nil, gstatus.Error(codes.InvalidArgument, "Missing a required identifier attribute")
}
filePath := s.buildFilePathFromBundleArgs(identifier.Extension, identifier.BundleKey)
filePath := s.buildFilePathFromBundleArgs(identifier.Extension, identifier.BundleKey, false)
record := proto.SettingsBundle{}
if err := s.parseRecordFromFile(&record, filePath); err != nil {
return nil, err
@@ -70,12 +62,7 @@ func (s Store) ReadBundle(identifier *proto.Identifier) (*proto.SettingsBundle,
// WriteBundle writes the given record into a file within the mountPath
// Extension and BundleKey within the record identifier are required.
func (s Store) WriteBundle(record *proto.SettingsBundle) (*proto.SettingsBundle, error) {
if len(record.Identifier.Extension) < 1 || len(record.Identifier.BundleKey) < 1 {
s.Logger.Error().Msg("extension and bundleKey cannot be empty")
return nil, gstatus.Error(codes.InvalidArgument, "Missing a required identifier attribute")
}
filePath := s.buildFilePathFromBundle(record)
filePath := s.buildFilePathFromBundle(record, true)
if err := s.writeRecordToFile(record, filePath); err != nil {
return nil, err
}

View File

@@ -10,41 +10,40 @@ import (
const folderNameBundles = "bundles"
const folderNameValues = "values"
// Builds the folder path for storing settings bundles
func (s Store) buildFolderPathBundles() string {
// Builds the folder path for storing settings bundles. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFolderPathBundles(mkdir bool) string {
folderPath := path.Join(s.mountPath, folderNameBundles)
s.ensureFolderExists(folderPath)
if mkdir {
s.ensureFolderExists(folderPath)
}
return folderPath
}
// Builds a unique file name from the given settings bundle
func (s Store) buildFilePathFromBundle(bundle *proto.SettingsBundle) string {
return s.buildFilePathFromBundleArgs(bundle.Identifier.Extension, bundle.Identifier.BundleKey)
// Builds a unique file name from the given settings bundle. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFilePathFromBundle(bundle *proto.SettingsBundle, mkdir bool) string {
return s.buildFilePathFromBundleArgs(bundle.Identifier.Extension, bundle.Identifier.BundleKey, mkdir)
}
// Builds a unique file name from the given params
func (s Store) buildFilePathFromBundleArgs(extension string, bundleKey string) string {
// 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) buildFilePathFromBundleArgs(extension string, bundleKey string, mkdir bool) string {
extensionFolder := path.Join(s.mountPath, folderNameBundles, extension)
s.ensureFolderExists(extensionFolder)
if mkdir {
s.ensureFolderExists(extensionFolder)
}
return path.Join(extensionFolder, bundleKey+".json")
}
// // Builds the folder path for storing settings values
// func (s Store) buildFolderPathValues() string {
// folderPath := path.Join(s.mountPath, folderNameValues)
// s.ensureFolderExists(folderPath)
// return folderPath
// }
// Builds a unique file name from the given settings value
func (s Store) buildFilePathFromValue(value *proto.SettingsValue) string {
return s.buildFilePathFromValueArgs(value.Identifier.AccountUuid, value.Identifier.Extension, value.Identifier.BundleKey)
// Builds a unique file name from the given settings value. If mkdir is true, folders in the path will be created if necessary.
func (s Store) buildFilePathFromValue(value *proto.SettingsValue, mkdir bool) string {
return s.buildFilePathFromValueArgs(value.Identifier.AccountUuid, value.Identifier.Extension, value.Identifier.BundleKey, mkdir)
}
// Builds a unique file name from the given params
func (s Store) buildFilePathFromValueArgs(accountUUID string, extension string, bundleKey string) string {
// 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) buildFilePathFromValueArgs(accountUUID string, extension string, bundleKey string, mkdir bool) string {
extensionFolder := path.Join(s.mountPath, folderNameValues, accountUUID, extension)
s.ensureFolderExists(extensionFolder)
if mkdir {
s.ensureFolderExists(extensionFolder)
}
return path.Join(extensionFolder, bundleKey+".json")
}

View File

@@ -14,12 +14,7 @@ import (
// ReadValue tries to find a value by the given identifier attributes within the mountPath
// All identifier fields are required.
func (s Store) ReadValue(identifier *proto.Identifier) (*proto.SettingsValue, error) {
if len(identifier.AccountUuid) < 1 || len(identifier.Extension) < 1 || len(identifier.BundleKey) < 1 || len(identifier.SettingKey) < 1 {
s.Logger.Error().Msg("account-uuid, extension, bundle and setting are required")
return nil, gstatus.Errorf(codes.InvalidArgument, "Missing a required identifier attribute")
}
filePath := s.buildFilePathFromValueArgs(identifier.AccountUuid, identifier.Extension, identifier.BundleKey)
filePath := s.buildFilePathFromValueArgs(identifier.AccountUuid, identifier.Extension, identifier.BundleKey, false)
values, err := s.readValuesMapFromFile(filePath)
if err != nil {
return nil, err
@@ -34,12 +29,7 @@ func (s Store) ReadValue(identifier *proto.Identifier) (*proto.SettingsValue, er
// WriteValue writes the given SettingsValue into a file within the mountPath
// All identifier fields within the value are required.
func (s Store) WriteValue(value *proto.SettingsValue) (*proto.SettingsValue, error) {
if len(value.Identifier.AccountUuid) < 1 || len(value.Identifier.Extension) < 1 || len(value.Identifier.BundleKey) < 1 || len(value.Identifier.SettingKey) < 1 {
s.Logger.Error().Msg("all identifier keys are required")
return nil, gstatus.Errorf(codes.InvalidArgument, "Missing a required identifier attribute")
}
filePath := s.buildFilePathFromValue(value)
filePath := s.buildFilePathFromValue(value, true)
values, err := s.readValuesMapFromFile(filePath)
if err != nil {
return nil, err
@@ -54,18 +44,12 @@ func (s Store) WriteValue(value *proto.SettingsValue) (*proto.SettingsValue, err
// ListValues reads all values within the scope of the given identifier
// AccountUuid is required.
func (s Store) ListValues(identifier *proto.Identifier) ([]*proto.SettingsValue, error) {
if len(identifier.AccountUuid) < 1 {
s.Logger.Error().Msg("account-uuid is required")
return nil, gstatus.Errorf(codes.InvalidArgument, "Missing a required identifier attribute")
}
accountFolderPath := path.Join(s.mountPath, folderNameValues, identifier.AccountUuid)
var values []*proto.SettingsValue
if _, err := os.Stat(accountFolderPath); err != nil {
return values, nil
}
// TODO: might be a good idea to do this non-hierarchical. i.e. allowing all fragments in the identifier being set or not.
// depending on the set values in the identifier arg, collect all SettingValues files for the account
var valueFilePaths []string
if len(identifier.Extension) < 1 {
@@ -77,7 +61,7 @@ func (s Store) ListValues(identifier *proto.Identifier) ([]*proto.SettingsValue,
return nil
}); err != nil {
s.Logger.Err(err).Msgf("error reading %v", accountFolderPath)
return nil, err
return values, nil
}
} else if len(identifier.BundleKey) < 1 {
extensionPath := path.Join(accountFolderPath, identifier.Extension)
@@ -89,7 +73,7 @@ func (s Store) ListValues(identifier *proto.Identifier) ([]*proto.SettingsValue,
return nil
}); err != nil {
s.Logger.Err(err).Msgf("error reading %v", extensionPath)
return nil, err
return values, nil
}
} else {
bundlePath := path.Join(accountFolderPath, identifier.Extension, identifier.BundleKey+".json")