mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-04-26 10:18:42 -04:00
Merge pull request #22 from owncloud/input-validation
Input validation (part 1)
This commit is contained in:
10
changelog/unreleased/input-validation.md
Normal file
10
changelog/unreleased/input-validation.md
Normal 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
4
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
|
||||
|
||||
11
go.sum
11
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=
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
87
pkg/service/v0/validator.go
Normal file
87
pkg/service/v0/validator.go
Normal 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...),
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user