rename folder extensions -> services
Signed-off-by: Christian Richter <crichter@owncloud.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
// Package store implements the go-micro store interface
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/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) {
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
assIDs, err := s.mdc.ReadDir(ctx, accountPath(accountUUID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ass := make([]*settingsmsg.UserRoleAssignment, 0, len(assIDs))
|
||||
for _, assID := range assIDs {
|
||||
b, err := s.mdc.SimpleDownload(ctx, assignmentPath(accountUUID, assID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a := &settingsmsg.UserRoleAssignment{}
|
||||
err = json.Unmarshal(b, a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ass = append(ass, a)
|
||||
}
|
||||
return ass, nil
|
||||
}
|
||||
|
||||
// WriteRoleAssignment appends the given role assignment to the existing assignments of the respective account.
|
||||
func (s *Store) WriteRoleAssignment(accountUUID, roleID string) (*settingsmsg.UserRoleAssignment, error) {
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
// as per https://github.com/owncloud/product/issues/103 "Each user can have exactly one role"
|
||||
_ = s.mdc.Delete(ctx, accountPath(accountUUID))
|
||||
// TODO: How to differentiate between 'not found' and other errors?
|
||||
|
||||
err := s.mdc.MakeDirIfNotExist(ctx, accountPath(accountUUID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ass := &settingsmsg.UserRoleAssignment{
|
||||
Id: uuid.Must(uuid.NewV4()).String(),
|
||||
AccountUuid: accountUUID,
|
||||
RoleId: roleID,
|
||||
}
|
||||
b, err := json.Marshal(ass)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ass, s.mdc.SimpleUpload(ctx, assignmentPath(accountUUID, ass.Id), b)
|
||||
}
|
||||
|
||||
// RemoveRoleAssignment deletes the given role assignment from the existing assignments of the respective account.
|
||||
func (s *Store) RemoveRoleAssignment(assignmentID string) error {
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
accounts, err := s.mdc.ReadDir(ctx, accountsFolderLocation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: use indexer to avoid spamming Metadata service
|
||||
for _, accID := range accounts {
|
||||
assIDs, err := s.mdc.ReadDir(ctx, accountPath(accID))
|
||||
if err != nil {
|
||||
// TODO: error?
|
||||
continue
|
||||
}
|
||||
|
||||
for _, assID := range assIDs {
|
||||
if assID == assignmentID {
|
||||
return s.mdc.Delete(ctx, assignmentPath(accID, assID))
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("assignmentID '%s' not found", assignmentID)
|
||||
}
|
||||
|
||||
func accountPath(accountUUID string) string {
|
||||
return fmt.Sprintf("%s/%s", accountsFolderLocation, accountUUID)
|
||||
}
|
||||
|
||||
func assignmentPath(accountUUID string, assignmentID string) string {
|
||||
return fmt.Sprintf("%s/%s/%s", accountsFolderLocation, accountUUID, assignmentID)
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/owncloud/ocis/v2/extensions/settings/pkg/config/defaults"
|
||||
olog "github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/shared"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
einstein = "a4d07560-a670-4be9-8d60-9b547751a208"
|
||||
//marie = "3c054db3-eec1-4ca4-b985-bc56dcf560cb"
|
||||
|
||||
s = &Store{
|
||||
Logger: logger,
|
||||
l: &sync.Mutex{},
|
||||
}
|
||||
|
||||
logger = olog.NewLogger(
|
||||
olog.Color(true),
|
||||
olog.Pretty(true),
|
||||
olog.Level("info"),
|
||||
)
|
||||
|
||||
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{
|
||||
{
|
||||
Id: "updateID",
|
||||
Name: "update",
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_UPDATE,
|
||||
},
|
||||
},
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SETTING,
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: "readID",
|
||||
Name: "read",
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READ,
|
||||
},
|
||||
},
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_BUNDLE,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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{
|
||||
{
|
||||
Id: "readID",
|
||||
Name: "read",
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READ,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
s.cfg = defaults.DefaultConfig()
|
||||
s.cfg.Commons = &shared.Commons{
|
||||
AdminUserID: uuid.Must(uuid.NewV4()).String(),
|
||||
}
|
||||
|
||||
_ = NewMDC(s)
|
||||
setupRoles()
|
||||
}
|
||||
|
||||
func setupRoles() {
|
||||
for i := range bundles {
|
||||
if _, err := s.WriteBundle(bundles[i]); err != nil {
|
||||
log.Fatal("error initializing ", 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)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, firstAssignment.RoleId, scenario.firstRole)
|
||||
// TODO: check entry exists
|
||||
|
||||
list, err := s.ListRoleAssignments(scenario.userID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(list))
|
||||
require.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)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, secondAssignment.RoleId, scenario.secondRole)
|
||||
|
||||
list, err = s.ListRoleAssignments(scenario.userID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(list))
|
||||
require.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)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, assignment.RoleId, scenario.firstRole)
|
||||
// TODO: uncomment
|
||||
// require.True(t, mdc.IDExists(assignment.RoleId))
|
||||
|
||||
list, err := s.ListRoleAssignments(scenario.userID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(list))
|
||||
require.Equal(t, assignment.Id, list[0].Id)
|
||||
|
||||
err = s.RemoveRoleAssignment(assignment.Id)
|
||||
require.NoError(t, err)
|
||||
// TODO: uncomment
|
||||
// require.False(t, mdc.IDExists(assignment.RoleId))
|
||||
|
||||
list, err = s.ListRoleAssignments(scenario.userID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(list))
|
||||
|
||||
err = s.RemoveRoleAssignment(assignment.Id)
|
||||
require.Error(t, err)
|
||||
// TODO: do we want a custom error message?
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// Package store implements the go-micro store interface
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/owncloud/ocis/v2/extensions/settings/pkg/store/defaults"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
// TODO: this is needed for initialization - we need to find a better way to fix this
|
||||
if s.mdc == nil && len(bundleIDs) == 1 {
|
||||
return defaultBundle(bundleType, bundleIDs[0]), nil
|
||||
}
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
|
||||
if len(bundleIDs) == 0 {
|
||||
bIDs, err := s.mdc.ReadDir(ctx, bundleFolderLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bundleIDs = bIDs
|
||||
}
|
||||
var bundles []*settingsmsg.Bundle
|
||||
for _, id := range bundleIDs {
|
||||
b, err := s.mdc.SimpleDownload(ctx, bundlePath(id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bundle := &settingsmsg.Bundle{}
|
||||
err = json.Unmarshal(b, bundle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bundle.Type == bundleType {
|
||||
bundles = append(bundles, bundle)
|
||||
}
|
||||
|
||||
}
|
||||
return bundles, nil
|
||||
}
|
||||
|
||||
// ReadBundle tries to find a bundle by the given id from the metadata service
|
||||
func (s *Store) ReadBundle(bundleID string) (*settingsmsg.Bundle, error) {
|
||||
if s.mdc == nil {
|
||||
return defaultBundle(settingsmsg.Bundle_TYPE_ROLE, bundleID)[0], nil
|
||||
}
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
b, err := s.mdc.SimpleDownload(ctx, bundlePath(bundleID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bundle := &settingsmsg.Bundle{}
|
||||
return bundle, json.Unmarshal(b, bundle)
|
||||
}
|
||||
|
||||
// ReadSetting tries to find a setting by the given id from the metadata service
|
||||
func (s *Store) ReadSetting(settingID string) (*settingsmsg.Setting, error) {
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
|
||||
ids, err := s.mdc.ReadDir(ctx, bundleFolderLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: avoid spamming metadata service
|
||||
for _, id := range ids {
|
||||
b, err := s.ReadBundle(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, setting := range b.Settings {
|
||||
if setting.Id == settingID {
|
||||
return setting, nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil, fmt.Errorf("setting '%s' not found", settingID)
|
||||
}
|
||||
|
||||
// WriteBundle sends the givens record to the metadataclient. returns `record` for legacy reasons
|
||||
func (s *Store) WriteBundle(record *settingsmsg.Bundle) (*settingsmsg.Bundle, error) {
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
|
||||
b, err := json.Marshal(record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return record, s.mdc.SimpleUpload(ctx, bundlePath(record.Id), b)
|
||||
}
|
||||
|
||||
// AddSettingToBundle adds the given setting to the bundle with the given bundleID.
|
||||
func (s *Store) AddSettingToBundle(bundleID string, setting *settingsmsg.Setting) (*settingsmsg.Setting, error) {
|
||||
s.Init()
|
||||
b, err := s.ReadBundle(bundleID)
|
||||
if err != nil {
|
||||
// TODO: How to differentiate 'not found'?
|
||||
b = new(settingsmsg.Bundle)
|
||||
b.Id = bundleID
|
||||
b.Type = settingsmsg.Bundle_TYPE_DEFAULT
|
||||
}
|
||||
|
||||
if setting.Id == "" {
|
||||
setting.Id = uuid.Must(uuid.NewV4()).String()
|
||||
}
|
||||
|
||||
b.Settings = append(b.Settings, setting)
|
||||
_, err = s.WriteBundle(b)
|
||||
return setting, err
|
||||
}
|
||||
|
||||
// RemoveSettingFromBundle removes the setting from the bundle with the given ids.
|
||||
func (s *Store) RemoveSettingFromBundle(bundleID string, settingID string) error {
|
||||
fmt.Println("RemoveSettingFromBundle not implemented")
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func bundlePath(id string) string {
|
||||
return fmt.Sprintf("%s/%s", bundleFolderLocation, id)
|
||||
}
|
||||
|
||||
func defaultBundle(bundleType settingsmsg.Bundle_Type, bundleID string) []*settingsmsg.Bundle {
|
||||
var bundles []*settingsmsg.Bundle
|
||||
for _, b := range defaults.GenerateBundlesDefaultRoles() {
|
||||
if b.Type == bundleType && b.Id == bundleID {
|
||||
bundles = append(bundles, b)
|
||||
}
|
||||
}
|
||||
return bundles
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
appendTestBundleID = uuid.Must(uuid.NewV4()).String()
|
||||
|
||||
appendTestSetting1 = &settingsmsg.Setting{
|
||||
Id: "append-test-setting-1",
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
appendTestSetting2 = &settingsmsg.Setting{
|
||||
Id: "append-test-setting-2",
|
||||
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) {
|
||||
for i := range bundleScenarios {
|
||||
b := bundleScenarios[i]
|
||||
t.Run(b.name, func(t *testing.T) {
|
||||
_, err := s.WriteBundle(b.bundle)
|
||||
require.NoError(t, err)
|
||||
bundle, err := s.ReadBundle(b.bundle.Id)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, b.bundle, bundle)
|
||||
})
|
||||
}
|
||||
|
||||
// check that ListBundles only returns bundles with type DEFAULT
|
||||
bundles, err := s.ListBundles(settingsmsg.Bundle_TYPE_DEFAULT, []string{})
|
||||
require.NoError(t, err)
|
||||
for i := range bundles {
|
||||
require.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})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(filteredBundles))
|
||||
if len(filteredBundles) == 1 {
|
||||
require.Equal(t, bundle2, filteredBundles[0].Id)
|
||||
}
|
||||
|
||||
// check that ListRoles only returns bundles with type ROLE
|
||||
roles, err := s.ListBundles(settingsmsg.Bundle_TYPE_ROLE, []string{})
|
||||
require.NoError(t, err)
|
||||
for i := range roles {
|
||||
require.Equal(t, settingsmsg.Bundle_TYPE_ROLE, roles[i].Type)
|
||||
}
|
||||
|
||||
// check that ReadSetting works
|
||||
setting, err := s.ReadSetting(setting1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test-desc-1", setting.Description) // could be tested better ;)
|
||||
}
|
||||
|
||||
func TestAppendSetting(t *testing.T) {
|
||||
//mdc := NewMDC()
|
||||
//s := Store{
|
||||
//Logger: olog.NewLogger(
|
||||
//olog.Color(true),
|
||||
//olog.Pretty(true),
|
||||
//olog.Level("info"),
|
||||
//),
|
||||
|
||||
//l: &sync.Mutex{},
|
||||
//mdc: mdc,
|
||||
//}
|
||||
|
||||
// appending to non existing bundle creates new
|
||||
_, err := s.AddSettingToBundle(appendTestBundleID, appendTestSetting1)
|
||||
require.NoError(t, err)
|
||||
|
||||
b, err := s.ReadBundle(appendTestBundleID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, b.Settings, 1)
|
||||
|
||||
_, err = s.AddSettingToBundle(appendTestBundleID, appendTestSetting2)
|
||||
require.NoError(t, err)
|
||||
|
||||
b, err = s.ReadBundle(appendTestBundleID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, b.Settings, 2)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ReneKroon/ttlcache/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
cachettl = 0
|
||||
// these need to be global instances for now as the `Service` (and therefore the `Store`) are instantiated twice (for grpc and http)
|
||||
// therefore caches need to cover both instances
|
||||
dircache = initCache(cachettl)
|
||||
filescache = initCache(cachettl)
|
||||
)
|
||||
|
||||
// CachedMDC is cache for the metadataclient
|
||||
type CachedMDC struct {
|
||||
next MetadataClient
|
||||
|
||||
files *ttlcache.Cache
|
||||
dirs *ttlcache.Cache
|
||||
}
|
||||
|
||||
// SimpleDownload caches the answer from SimpleDownload or returns the cached one
|
||||
func (c *CachedMDC) SimpleDownload(ctx context.Context, id string) ([]byte, error) {
|
||||
if b, err := c.files.Get(id); err == nil {
|
||||
return b.([]byte), nil
|
||||
}
|
||||
b, err := c.next.SimpleDownload(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_ = c.files.Set(id, b)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// SimpleUpload caches the answer from SimpleUpload and invalidates the cache
|
||||
func (c *CachedMDC) SimpleUpload(ctx context.Context, id string, content []byte) error {
|
||||
b, err := c.files.Get(id)
|
||||
if err == nil && string(b.([]byte)) == string(content) {
|
||||
// no need to bug mdc
|
||||
return nil
|
||||
}
|
||||
|
||||
err = c.next.SimpleUpload(ctx, id, content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// invalidate caches
|
||||
_ = c.dirs.Remove(path.Dir(id))
|
||||
_ = c.files.Set(id, content)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete invalidates the cache when operation was successful
|
||||
func (c *CachedMDC) Delete(ctx context.Context, id string) error {
|
||||
if err := c.next.Delete(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// invalidate caches
|
||||
_ = removePrefix(c.files, id)
|
||||
_ = removePrefix(c.dirs, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadDir caches the response from ReadDir or returnes the cached one
|
||||
func (c *CachedMDC) ReadDir(ctx context.Context, id string) ([]string, error) {
|
||||
i, err := c.dirs.Get(id)
|
||||
if err == nil {
|
||||
return i.([]string), nil
|
||||
}
|
||||
|
||||
s, err := c.next.ReadDir(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, c.dirs.Set(id, s)
|
||||
}
|
||||
|
||||
// MakeDirIfNotExist invalidates the cache
|
||||
func (c *CachedMDC) MakeDirIfNotExist(ctx context.Context, id string) error {
|
||||
err := c.next.MakeDirIfNotExist(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// invalidate caches
|
||||
_ = c.dirs.Remove(path.Dir(id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init instantiates the caches
|
||||
func (c *CachedMDC) Init(ctx context.Context, id string) error {
|
||||
c.dirs = dircache
|
||||
c.files = filescache
|
||||
return c.next.Init(ctx, id)
|
||||
}
|
||||
|
||||
func initCache(ttlSeconds int) *ttlcache.Cache {
|
||||
cache := ttlcache.NewCache()
|
||||
_ = cache.SetTTL(time.Duration(ttlSeconds) * time.Second)
|
||||
cache.SkipTTLExtensionOnHit(true)
|
||||
return cache
|
||||
}
|
||||
|
||||
func removePrefix(cache *ttlcache.Cache, prefix string) error {
|
||||
for _, k := range cache.GetKeys() {
|
||||
if strings.HasPrefix(k, prefix) {
|
||||
if err := cache.Remove(k); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/owncloud/ocis/v2/extensions/settings/pkg/settings"
|
||||
"github.com/owncloud/ocis/v2/extensions/settings/pkg/util"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/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) {
|
||||
records := make([]*settingsmsg.Permission, 0)
|
||||
for _, roleID := range roleIDs {
|
||||
role, err := s.ReadBundle(roleID)
|
||||
if err != nil {
|
||||
s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping")
|
||||
continue
|
||||
}
|
||||
records = append(records, extractPermissionsByResource(resource, role)...)
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// ReadPermissionByID finds the permission in the roles, specified by the provided roleIDs
|
||||
func (s *Store) ReadPermissionByID(permissionID string, roleIDs []string) (*settingsmsg.Permission, error) {
|
||||
for _, roleID := range roleIDs {
|
||||
role, err := s.ReadBundle(roleID)
|
||||
if err != nil {
|
||||
s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping")
|
||||
continue
|
||||
}
|
||||
for _, permission := range role.Settings {
|
||||
if permission.Id == permissionID {
|
||||
if value, ok := permission.Value.(*settingsmsg.Setting_PermissionValue); ok {
|
||||
return value.PermissionValue, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ReadPermissionByName finds the permission in the roles, specified by the provided roleIDs
|
||||
func (s *Store) ReadPermissionByName(name string, roleIDs []string) (*settingsmsg.Permission, error) {
|
||||
for _, roleID := range roleIDs {
|
||||
role, err := s.ReadBundle(roleID)
|
||||
if err != nil {
|
||||
s.Logger.Debug().Str("roleID", roleID).Msg("role not found, skipping")
|
||||
continue
|
||||
}
|
||||
for _, permission := range role.Settings {
|
||||
if permission.Name == name {
|
||||
if value, ok := permission.Value.(*settingsmsg.Setting_PermissionValue); ok {
|
||||
return value.PermissionValue, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, settings.ErrPermissionNotFound
|
||||
}
|
||||
|
||||
// extractPermissionsByResource collects all permissions from the provided role that match the requested resource
|
||||
func extractPermissionsByResource(resource *settingsmsg.Resource, role *settingsmsg.Bundle) []*settingsmsg.Permission {
|
||||
permissions := make([]*settingsmsg.Permission, 0)
|
||||
for _, setting := range role.Settings {
|
||||
if value, ok := setting.Value.(*settingsmsg.Setting_PermissionValue); ok {
|
||||
if util.IsResourceMatched(setting.Resource, resource) {
|
||||
permissions = append(permissions, value.PermissionValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
return permissions
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPermission(t *testing.T) {
|
||||
// bunldes are initialized within init func
|
||||
p, err := s.ReadPermissionByID("readID", []string{"f36db5e6-a03c-40df-8413-711c67e40b47"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, settingsmsg.Permission_OPERATION_READ, p.Operation)
|
||||
|
||||
p, err = s.ReadPermissionByName("read", []string{"f36db5e6-a03c-40df-8413-711c67e40b47"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, settingsmsg.Permission_OPERATION_READ, p.Operation)
|
||||
|
||||
pms, err := s.ListPermissionsByResource(&settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_BUNDLE,
|
||||
}, []string{"f36db5e6-a03c-40df-8413-711c67e40b47"})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, pms, 1)
|
||||
require.Equal(t, settingsmsg.Permission_OPERATION_READ, pms[0].Operation)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
// Package store implements the go-micro store interface
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/cs3org/reva/v2/pkg/storage/utils/metadata"
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/owncloud/ocis/v2/extensions/settings/pkg/config"
|
||||
"github.com/owncloud/ocis/v2/extensions/settings/pkg/settings"
|
||||
"github.com/owncloud/ocis/v2/extensions/settings/pkg/store/defaults"
|
||||
olog "github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
)
|
||||
|
||||
var (
|
||||
// Name is the default name for the settings store
|
||||
Name = "ocis-settings"
|
||||
managerName = "metadata"
|
||||
settingsSpaceID = "f1bdd61a-da7c-49fc-8203-0558109d1b4f" // uuid.Must(uuid.NewV4()).String()
|
||||
rootFolderLocation = "settings"
|
||||
bundleFolderLocation = "settings/bundles"
|
||||
accountsFolderLocation = "settings/accounts"
|
||||
valuesFolderLocation = "settings/values"
|
||||
)
|
||||
|
||||
// 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
|
||||
ReadDir(ctx context.Context, id string) ([]string, error)
|
||||
MakeDirIfNotExist(ctx context.Context, id string) error
|
||||
Init(ctx context.Context, id string) error
|
||||
}
|
||||
|
||||
// Store interacts with the filesystem to manage settings information
|
||||
type Store struct {
|
||||
Logger olog.Logger
|
||||
|
||||
mdc MetadataClient
|
||||
cfg *config.Config
|
||||
|
||||
l *sync.Mutex
|
||||
}
|
||||
|
||||
// Init initialize the store once, later calls are noops
|
||||
func (s *Store) Init() {
|
||||
if s.mdc != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
|
||||
if s.mdc != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mdc := &CachedMDC{next: NewMetadataClient(s.cfg.Metadata)}
|
||||
if err := s.initMetadataClient(mdc); err != nil {
|
||||
s.Logger.Error().Err(err).Msg("error initializing metadata client")
|
||||
}
|
||||
}
|
||||
|
||||
// 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),
|
||||
),
|
||||
cfg: cfg,
|
||||
l: &sync.Mutex{},
|
||||
}
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
// NewMetadataClient returns the MetadataClient
|
||||
func NewMetadataClient(cfg config.Metadata) MetadataClient {
|
||||
mdc, err := metadata.NewCS3Storage(cfg.GatewayAddress, cfg.StorageAddress, cfg.SystemUserID, cfg.SystemUserIDP, cfg.SystemUserAPIKey)
|
||||
if err != nil {
|
||||
log.Fatal("error connecting to mdc:", err)
|
||||
}
|
||||
return mdc
|
||||
|
||||
}
|
||||
|
||||
// we need to lazy initialize the MetadataClient because metadata service might not be ready
|
||||
func (s *Store) initMetadataClient(mdc MetadataClient) error {
|
||||
ctx := context.TODO()
|
||||
err := mdc.Init(ctx, settingsSpaceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range []string{
|
||||
rootFolderLocation,
|
||||
accountsFolderLocation,
|
||||
bundleFolderLocation,
|
||||
valuesFolderLocation,
|
||||
} {
|
||||
err = mdc.MakeDirIfNotExist(ctx, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range defaults.GenerateBundlesDefaultRoles() {
|
||||
b, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = mdc.SimpleUpload(ctx, bundlePath(p.Id), b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range defaults.DefaultRoleAssignments(s.cfg) {
|
||||
accountUUID := p.AccountUuid
|
||||
roleID := p.RoleId
|
||||
err = mdc.MakeDirIfNotExist(ctx, accountPath(accountUUID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ass := &settingsmsg.UserRoleAssignment{
|
||||
Id: uuid.Must(uuid.NewV4()).String(),
|
||||
AccountUuid: accountUUID,
|
||||
RoleId: roleID,
|
||||
}
|
||||
b, err := json.Marshal(ass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = mdc.SimpleUpload(ctx, assignmentPath(accountUUID, ass.Id), b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.mdc = mdc
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
settings.Registry[managerName] = New
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/owncloud/ocis/v2/extensions/settings/pkg/config/defaults"
|
||||
)
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// use "unit" or "integration" do define test type. You need a running ocis instance for integration tests
|
||||
var testtype = "unit"
|
||||
|
||||
// MockedMetadataClient mocks the metadataservice inmemory
|
||||
type MockedMetadataClient struct {
|
||||
data map[string][]byte
|
||||
}
|
||||
|
||||
// NewMDC instantiates a mocked MetadataClient
|
||||
func NewMDC(s *Store) error {
|
||||
var mdc MetadataClient
|
||||
switch testtype {
|
||||
case "unit":
|
||||
mdc = &MockedMetadataClient{data: make(map[string][]byte)}
|
||||
case "integration":
|
||||
mdc = NewMetadataClient(defaults.DefaultConfig().Metadata)
|
||||
}
|
||||
return s.initMetadataClient(mdc)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
for k := range m.data {
|
||||
if strings.HasPrefix(k, id) {
|
||||
delete(m.data, k)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadDir returns nil, nil if not found
|
||||
func (m *MockedMetadataClient) ReadDir(_ context.Context, id string) ([]string, error) {
|
||||
var out []string
|
||||
for k := range m.data {
|
||||
if strings.HasPrefix(k, id) {
|
||||
dir := strings.TrimPrefix(k, id+"/")
|
||||
// filter subfolders the lame way
|
||||
s := strings.Trim(strings.SplitAfter(dir, "/")[0], "/")
|
||||
out = append(out, s)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// MakeDirIfNotExist does nothing
|
||||
func (*MockedMetadataClient) MakeDirIfNotExist(_ context.Context, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Init does nothing
|
||||
func (*MockedMetadataClient) Init(_ context.Context, _ string) error {
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// Package store implements the go-micro store interface
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/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) {
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
|
||||
vIDs, err := s.mdc.ReadDir(ctx, valuesFolderLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: refine logic not to spam metadata service
|
||||
var values []*settingsmsg.Value
|
||||
for _, vid := range vIDs {
|
||||
b, err := s.mdc.SimpleDownload(ctx, valuePath(vid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := &settingsmsg.Value{}
|
||||
err = json.Unmarshal(b, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bundleID != "" && v.BundleId != bundleID {
|
||||
continue
|
||||
}
|
||||
|
||||
if v.AccountUuid == "" {
|
||||
values = append(values, v)
|
||||
continue
|
||||
}
|
||||
|
||||
if v.AccountUuid == accountUUID {
|
||||
values = append(values, v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// ReadValue tries to find a value by the given valueId within the dataPath
|
||||
func (s *Store) ReadValue(valueID string) (*settingsmsg.Value, error) {
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
|
||||
b, err := s.mdc.SimpleDownload(ctx, valuePath(valueID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
val := &settingsmsg.Value{}
|
||||
return val, json.Unmarshal(b, val)
|
||||
}
|
||||
|
||||
// ReadValueByUniqueIdentifiers tries to find a value given a set of unique identifiers
|
||||
func (s *Store) ReadValueByUniqueIdentifiers(accountUUID, settingID string) (*settingsmsg.Value, error) {
|
||||
fmt.Println("ReadValueByUniqueIdentifiers not implemented")
|
||||
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) {
|
||||
s.Init()
|
||||
ctx := context.TODO()
|
||||
|
||||
if value.Id == "" {
|
||||
value.Id = uuid.Must(uuid.NewV4()).String()
|
||||
}
|
||||
b, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return value, s.mdc.SimpleUpload(ctx, valuePath(value.Id), b)
|
||||
}
|
||||
|
||||
func valuePath(id string) string {
|
||||
return fmt.Sprintf("%s/%s", valuesFolderLocation, id)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
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: bundle2,
|
||||
SettingId: setting2,
|
||||
AccountUuid: accountUUID1,
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_FILE,
|
||||
Id: "adfba82d-919a-41c3-9cd1-5a3f83b2bf76",
|
||||
},
|
||||
Value: &settingsmsg.Value_StringValue{
|
||||
StringValue: "tralala",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "value without accountUUID",
|
||||
value: &settingsmsg.Value{
|
||||
Id: value3,
|
||||
BundleId: bundle3,
|
||||
SettingId: setting2,
|
||||
AccountUuid: "",
|
||||
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) {
|
||||
for i := range valueScenarios {
|
||||
index := i
|
||||
t.Run(valueScenarios[index].name, func(t *testing.T) {
|
||||
value := valueScenarios[index].value
|
||||
v, err := s.WriteValue(value)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, value, v)
|
||||
|
||||
v, err = s.ReadValue(value.Id)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, value, v)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListValues(t *testing.T) {
|
||||
for _, v := range valueScenarios {
|
||||
_, err := s.WriteValue(v.value)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// empty accountid returns only values with empty accountud
|
||||
vs, err := s.ListValues("", "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, vs, 1)
|
||||
|
||||
// filled accountid returns matching and empty accountUUID values
|
||||
vs, err = s.ListValues("", accountUUID1)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, vs, 3)
|
||||
|
||||
// filled bundleid only returns matching values
|
||||
vs, err = s.ListValues(bundle3, accountUUID1)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, vs, 1)
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user