diff --git a/changelog/unreleased/input-validation.md b/changelog/unreleased/input-validation.md new file mode 100644 index 0000000000..6d2f12cdb2 --- /dev/null +++ b/changelog/unreleased/input-validation.md @@ -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 diff --git a/go.mod b/go.mod index 9303d85b62..7e6996c6a4 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 2cdce11fed..3fff6bd82e 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/assets/embed.go b/pkg/assets/embed.go index 5f78f66517..76d0b90a42 100644 --- a/pkg/assets/embed.go +++ b/pkg/assets/embed.go @@ -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 diff --git a/pkg/proto/v0/settings.pb.micro_test.go b/pkg/proto/v0/settings.pb.micro_test.go index cef2b6fd05..7ff238665d 100644 --- a/pkg/proto/v0/settings.pb.micro_test.go +++ b/pkg/proto/v0/settings.pb.micro_test.go @@ -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") - -} diff --git a/pkg/service/v0/service.go b/pkg/service/v0/service.go index 06d93150ae..b5d56853ab 100644 --- a/pkg/service/v0/service.go +++ b/pkg/service/v0/service.go @@ -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 } diff --git a/pkg/service/v0/validator.go b/pkg/service/v0/validator.go new file mode 100644 index 0000000000..005efaa126 --- /dev/null +++ b/pkg/service/v0/validator.go @@ -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...), + ) +} diff --git a/pkg/store/filesystem/bundles.go b/pkg/store/filesystem/bundles.go index b8ad11a7f3..89a98a3dde 100644 --- a/pkg/store/filesystem/bundles.go +++ b/pkg/store/filesystem/bundles.go @@ -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 } diff --git a/pkg/store/filesystem/paths.go b/pkg/store/filesystem/paths.go index 5cc30d7047..9bb1c54755 100644 --- a/pkg/store/filesystem/paths.go +++ b/pkg/store/filesystem/paths.go @@ -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") } diff --git a/pkg/store/filesystem/values.go b/pkg/store/filesystem/values.go index 1dc05e85b5..04828fae12 100644 --- a/pkg/store/filesystem/values.go +++ b/pkg/store/filesystem/values.go @@ -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")