Settings: add metadata store

Signed-off-by: jkoberg <jkoberg@owncloud.com>
This commit is contained in:
jkoberg
2022-02-24 15:09:25 +01:00
parent d92dc8951a
commit 59527e118d
10 changed files with 634 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
// Package store implements the go-micro store interface
package store
import (
"errors"
settingsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/settings/v0"
)
// ListRoleAssignments loads and returns all role assignments matching the given assignment identifier.
func (s Store) ListRoleAssignments(accountUUID string) ([]*settingsmsg.UserRoleAssignment, error) {
return nil, errors.New("not implemented")
}
// WriteRoleAssignment appends the given role assignment to the existing assignments of the respective account.
func (s Store) WriteRoleAssignment(accountUUID, roleID string) (*settingsmsg.UserRoleAssignment, error) {
return nil, errors.New("not implemented")
}
// RemoveRoleAssignment deletes the given role assignment from the existing assignments of the respective account.
func (s Store) RemoveRoleAssignment(assignmentID string) error {
return errors.New("not implemented")
}

View File

@@ -0,0 +1,177 @@
package store
import (
"log"
"testing"
olog "github.com/owncloud/ocis/ocis-pkg/log"
settingsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/settings/v0"
"github.com/stretchr/testify/assert"
)
var (
einstein = "a4d07560-a670-4be9-8d60-9b547751a208"
//marie = "3c054db3-eec1-4ca4-b985-bc56dcf560cb"
s = Store{
Logger: logger,
mdc: mdc,
}
logger = olog.NewLogger(
olog.Color(true),
olog.Pretty(true),
olog.Level("info"),
)
mdc = &MockedMetadataClient{data: make(map[string][]byte)}
bundles = []*settingsmsg.Bundle{
{
Id: "f36db5e6-a03c-40df-8413-711c67e40b47",
Type: settingsmsg.Bundle_TYPE_ROLE,
DisplayName: "test role - reads | update",
Name: "TEST_ROLE",
Extension: "ocis-settings",
Resource: &settingsmsg.Resource{
Type: settingsmsg.Resource_TYPE_BUNDLE,
},
Settings: []*settingsmsg.Setting{
{
Name: "update",
Value: &settingsmsg.Setting_PermissionValue{
PermissionValue: &settingsmsg.Permission{
Operation: settingsmsg.Permission_OPERATION_UPDATE,
},
},
},
{
Name: "read",
Value: &settingsmsg.Setting_PermissionValue{
PermissionValue: &settingsmsg.Permission{
Operation: settingsmsg.Permission_OPERATION_READ,
},
},
},
},
},
{
Id: "44f1a664-0a7f-461a-b0be-5b59e46bbc7a",
Type: settingsmsg.Bundle_TYPE_ROLE,
DisplayName: "another",
Name: "ANOTHER_TEST_ROLE",
Extension: "ocis-settings",
Resource: &settingsmsg.Resource{
Type: settingsmsg.Resource_TYPE_BUNDLE,
},
Settings: []*settingsmsg.Setting{
{
Name: "read",
Value: &settingsmsg.Setting_PermissionValue{
PermissionValue: &settingsmsg.Permission{
Operation: settingsmsg.Permission_OPERATION_READ,
},
},
},
},
},
}
)
func init() {
setupRoles()
}
func setupRoles() {
for i := range bundles {
if _, err := s.WriteBundle(bundles[i]); err != nil {
log.Fatal(err)
}
}
}
func TestAssignmentUniqueness(t *testing.T) {
var scenarios = []struct {
name string
userID string
firstRole string
secondRole string
}{
{
"roles assignments",
einstein,
"f36db5e6-a03c-40df-8413-711c67e40b47",
"44f1a664-0a7f-461a-b0be-5b59e46bbc7a",
},
}
for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.name, func(t *testing.T) {
firstAssignment, err := s.WriteRoleAssignment(scenario.userID, scenario.firstRole)
assert.NoError(t, err)
assert.Equal(t, firstAssignment.RoleId, scenario.firstRole)
// TODO: check entry exists
list, err := s.ListRoleAssignments(scenario.userID)
assert.NoError(t, err)
assert.Equal(t, 1, len(list))
assert.Equal(t, list[0].RoleId, scenario.firstRole)
// creating another assignment shouldn't add another entry, as we support max one role per user.
// assigning the second role should remove the old
secondAssignment, err := s.WriteRoleAssignment(scenario.userID, scenario.secondRole)
assert.NoError(t, err)
assert.Equal(t, secondAssignment.RoleId, scenario.secondRole)
list, err = s.ListRoleAssignments(scenario.userID)
assert.NoError(t, err)
assert.Equal(t, 1, len(list))
assert.Equal(t, list[0].RoleId, scenario.secondRole)
})
}
}
func TestDeleteAssignment(t *testing.T) {
var scenarios = []struct {
name string
userID string
firstRole string
secondRole string
}{
{
"roles assignments",
einstein,
"f36db5e6-a03c-40df-8413-711c67e40b47",
"44f1a664-0a7f-461a-b0be-5b59e46bbc7a",
},
}
for _, scenario := range scenarios {
scenario := scenario
t.Run(scenario.name, func(t *testing.T) {
assignment, err := s.WriteRoleAssignment(scenario.userID, scenario.firstRole)
assert.NoError(t, err)
assert.Equal(t, assignment.RoleId, scenario.firstRole)
// TODO: uncomment
// assert.True(t, mdc.IDExists(assignment.RoleId))
list, err := s.ListRoleAssignments(scenario.userID)
assert.NoError(t, err)
assert.Equal(t, 1, len(list))
err = s.RemoveRoleAssignment(assignment.Id)
assert.NoError(t, err)
// TODO: uncomment
// assert.False(t, mdc.IDExists(assignment.RoleId))
list, err = s.ListRoleAssignments(scenario.userID)
assert.NoError(t, err)
assert.Equal(t, 0, len(list))
err = s.RemoveRoleAssignment(assignment.Id)
assert.Error(t, err)
// TODO: do we want a custom error message?
})
}
}

View File

@@ -0,0 +1,41 @@
// Package store implements the go-micro store interface
package store
import (
"errors"
"sync"
settingsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/settings/v0"
)
var m = &sync.RWMutex{}
// ListBundles returns all bundles in the dataPath folder that match the given type.
func (s Store) ListBundles(bundleType settingsmsg.Bundle_Type, bundleIDs []string) ([]*settingsmsg.Bundle, error) {
return nil, errors.New("not implemented")
}
// ReadBundle tries to find a bundle by the given id within the dataPath.
func (s Store) ReadBundle(bundleID string) (*settingsmsg.Bundle, error) {
return nil, errors.New("not implemented")
}
// ReadSetting tries to find a setting by the given id within the dataPath.
func (s Store) ReadSetting(settingID string) (*settingsmsg.Setting, error) {
return nil, errors.New("not implemented")
}
// WriteBundle writes the given record into a file within the dataPath.
func (s Store) WriteBundle(record *settingsmsg.Bundle) (*settingsmsg.Bundle, error) {
return nil, errors.New("not implemented")
}
// AddSettingToBundle adds the given setting to the bundle with the given bundleID.
func (s Store) AddSettingToBundle(bundleID string, setting *settingsmsg.Setting) (*settingsmsg.Setting, error) {
return nil, errors.New("not implemented")
}
// RemoveSettingFromBundle removes the setting from the bundle with the given ids.
func (s Store) RemoveSettingFromBundle(bundleID string, settingID string) error {
return errors.New("not implemented")
}

View File

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

View File

@@ -0,0 +1,22 @@
package store
import (
"errors"
settingsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/settings/v0"
)
// ListPermissionsByResource collects all permissions from the provided roleIDs that match the requested resource
func (s Store) ListPermissionsByResource(resource *settingsmsg.Resource, roleIDs []string) ([]*settingsmsg.Permission, error) {
return nil, errors.New("not implemented")
}
// ReadPermissionByID finds the permission in the roles, specified by the provided roleIDs
func (s Store) ReadPermissionByID(permissionID string, roleIDs []string) (*settingsmsg.Permission, error) {
return nil, errors.New("not implemented")
}
// ReadPermissionByName finds the permission in the roles, specified by the provided roleIDs
func (s Store) ReadPermissionByName(name string, roleIDs []string) (*settingsmsg.Permission, error) {
return nil, errors.New("not implemented")
}

View File

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

View File

@@ -0,0 +1,62 @@
package store
import "context"
const (
// account UUIDs
accountUUID1 = "c4572da7-6142-4383-8fc6-efde3d463036"
//accountUUID2 = "e11f9769-416a-427d-9441-41a0e51391d7"
//accountUUID3 = "633ecd77-1980-412a-8721-bf598a330bb4"
// extension names
extension1 = "test-extension-1"
extension2 = "test-extension-2"
// bundle ids
bundle1 = "2f06addf-4fd2-49d5-8f71-00fbd3a3ec47"
bundle2 = "2d745744-749c-4286-8e92-74a24d8331c5"
bundle3 = "d8fd27d1-c00b-4794-a658-416b756a72ff"
// setting ids
setting1 = "c7ebbc8b-d15a-4f2e-9d7d-d6a4cf858d1a"
setting2 = "3fd9a3d9-20b7-40d4-9294-b22bb5868c10"
setting3 = "24bb9535-3df4-42f1-a622-7c0562bec99f"
// value ids
value1 = "fd3b6221-dc13-4a22-824d-2480495f1cdb"
value2 = "2a0bd9b0-ca1d-491a-8c56-d2ddfd68ded8"
//value3 = "b42702d2-5e4d-4d73-b133-e1f9e285355e"
)
// MockedMetadataClient mocks the metadataservice inmemory
type MockedMetadataClient struct {
data map[string][]byte
}
// SimpleDownload returns nil if not found
func (m *MockedMetadataClient) SimpleDownload(_ context.Context, id string) ([]byte, error) {
return m.data[id], nil
}
// SimpleUpload can't error
func (m *MockedMetadataClient) SimpleUpload(_ context.Context, id string, content []byte) error {
m.data[id] = content
return nil
}
// Delete can't error either
func (m *MockedMetadataClient) Delete(_ context.Context, id string) error {
delete(m.data, id)
return nil
}
// IDExists is a helper to check if an id exists
func (m *MockedMetadataClient) IDExists(id string) bool {
_, ok := m.data[id]
return ok
}
// IDHasContent returns true if the value stored under id has the given content (converted to string)
func (m *MockedMetadataClient) IDHasContent(id string, content []byte) bool {
return string(m.data[id]) == string(content)
}

View File

@@ -0,0 +1,31 @@
// Package store implements the go-micro store interface
package store
import (
"errors"
settingsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/settings/v0"
)
// ListValues reads all values that match the given bundleId and accountUUID.
// If the bundleId is empty, it's ignored for filtering.
// If the accountUUID is empty, only values with empty accountUUID are returned.
// If the accountUUID is not empty, values with an empty or with a matching accountUUID are returned.
func (s Store) ListValues(bundleID, accountUUID string) ([]*settingsmsg.Value, error) {
return nil, errors.New("not implemented")
}
// ReadValue tries to find a value by the given valueId within the dataPath
func (s Store) ReadValue(valueID string) (*settingsmsg.Value, error) {
return nil, errors.New("not implemented")
}
// ReadValueByUniqueIdentifiers tries to find a value given a set of unique identifiers
func (s Store) ReadValueByUniqueIdentifiers(accountUUID, settingID string) (*settingsmsg.Value, error) {
return nil, errors.New("not implemented")
}
// WriteValue writes the given value into a file within the dataPath
func (s Store) WriteValue(value *settingsmsg.Value) (*settingsmsg.Value, error) {
return nil, errors.New("not implemented")
}

View File

@@ -0,0 +1,72 @@
package store
import (
"testing"
olog "github.com/owncloud/ocis/ocis-pkg/log"
settingsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/settings/v0"
"github.com/stretchr/testify/assert"
)
var valueScenarios = []struct {
name string
value *settingsmsg.Value
}{
{
name: "generic-test-with-system-resource",
value: &settingsmsg.Value{
Id: value1,
BundleId: bundle1,
SettingId: setting1,
AccountUuid: accountUUID1,
Resource: &settingsmsg.Resource{
Type: settingsmsg.Resource_TYPE_SYSTEM,
},
Value: &settingsmsg.Value_StringValue{
StringValue: "lalala",
},
},
},
{
name: "generic-test-with-file-resource",
value: &settingsmsg.Value{
Id: value2,
BundleId: bundle1,
SettingId: setting2,
AccountUuid: accountUUID1,
Resource: &settingsmsg.Resource{
Type: settingsmsg.Resource_TYPE_FILE,
Id: "adfba82d-919a-41c3-9cd1-5a3f83b2bf76",
},
Value: &settingsmsg.Value_StringValue{
StringValue: "tralala",
},
},
},
}
func TestValues(t *testing.T) {
mdc := &MockedMetadataClient{data: make(map[string][]byte)}
s := Store{
Logger: olog.NewLogger(
olog.Color(true),
olog.Pretty(true),
olog.Level("info"),
),
mdc: mdc,
}
for i := range valueScenarios {
index := i
t.Run(valueScenarios[index].name, func(t *testing.T) {
value := valueScenarios[index].value
v, err := s.WriteValue(value)
assert.NoError(t, err)
assert.Equal(t, value, v)
v, err = s.ReadValue(value.Id)
assert.NoError(t, err)
assert.Equal(t, value, v)
})
}
}

View File

@@ -3,4 +3,5 @@ package store
import (
// init filesystem store
_ "github.com/owncloud/ocis/settings/pkg/store/filesystem"
_ "github.com/owncloud/ocis/settings/pkg/store/metadata"
)