diff --git a/settings/pkg/store/metadata/assignments.go b/settings/pkg/store/metadata/assignments.go new file mode 100644 index 0000000000..fc9d4431f5 --- /dev/null +++ b/settings/pkg/store/metadata/assignments.go @@ -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") +} diff --git a/settings/pkg/store/metadata/assignments_test.go b/settings/pkg/store/metadata/assignments_test.go new file mode 100644 index 0000000000..654b8af25f --- /dev/null +++ b/settings/pkg/store/metadata/assignments_test.go @@ -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? + }) + } +} diff --git a/settings/pkg/store/metadata/bundles.go b/settings/pkg/store/metadata/bundles.go new file mode 100644 index 0000000000..29af5e9130 --- /dev/null +++ b/settings/pkg/store/metadata/bundles.go @@ -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") +} diff --git a/settings/pkg/store/metadata/bundles_test.go b/settings/pkg/store/metadata/bundles_test.go new file mode 100644 index 0000000000..13c889bc1b --- /dev/null +++ b/settings/pkg/store/metadata/bundles_test.go @@ -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) + } +} diff --git a/settings/pkg/store/metadata/permissions.go b/settings/pkg/store/metadata/permissions.go new file mode 100644 index 0000000000..7819aea38b --- /dev/null +++ b/settings/pkg/store/metadata/permissions.go @@ -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") +} diff --git a/settings/pkg/store/metadata/store.go b/settings/pkg/store/metadata/store.go new file mode 100644 index 0000000000..054c5fcde5 --- /dev/null +++ b/settings/pkg/store/metadata/store.go @@ -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 +} diff --git a/settings/pkg/store/metadata/store_test.go b/settings/pkg/store/metadata/store_test.go new file mode 100644 index 0000000000..3fe5be0bb0 --- /dev/null +++ b/settings/pkg/store/metadata/store_test.go @@ -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) +} diff --git a/settings/pkg/store/metadata/values.go b/settings/pkg/store/metadata/values.go new file mode 100644 index 0000000000..d7a78421ee --- /dev/null +++ b/settings/pkg/store/metadata/values.go @@ -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") +} diff --git a/settings/pkg/store/metadata/values_test.go b/settings/pkg/store/metadata/values_test.go new file mode 100644 index 0000000000..4881f95819 --- /dev/null +++ b/settings/pkg/store/metadata/values_test.go @@ -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) + + }) + } +} diff --git a/settings/pkg/store/registry.go b/settings/pkg/store/registry.go index 29186466e3..2359e0d392 100644 --- a/settings/pkg/store/registry.go +++ b/settings/pkg/store/registry.go @@ -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" )