Merge pull request #3232 from kobergj/StoreSettingsViaMetadata
[full-ci] Store settings via metadata
This commit is contained in:
@@ -71,7 +71,7 @@ func DefaultConfig() *config.Config {
|
||||
},
|
||||
ServiceUser: config.ServiceUser{
|
||||
UUID: "95cb8724-03b2-11eb-a0a6-c33ef8ef53ad",
|
||||
Username: "",
|
||||
Username: "95cb8724-03b2-11eb-a0a6-c33ef8ef53ad",
|
||||
UID: 0,
|
||||
GID: 0,
|
||||
},
|
||||
|
||||
@@ -99,33 +99,6 @@ func (s Service) hasSelfManagementPermissions(ctx context.Context) bool {
|
||||
return s.RoleManager.FindPermissionByID(ctx, roleIDs, SelfManagementPermissionID) != nil
|
||||
}
|
||||
|
||||
// serviceUserToIndex temporarily adds a service user to the index, which is supposed to be removed before the lock on the handler function is released
|
||||
func (s Service) serviceUserToIndex() (teardownServiceUser func()) {
|
||||
if s.Config.ServiceUser.Username != "" && s.Config.ServiceUser.UUID != "" {
|
||||
_, err := s.index.Add(s.getInMemoryServiceUser())
|
||||
if err != nil {
|
||||
s.log.Logger.Err(err).Msg("service user was configured but failed to be added to the index")
|
||||
} else {
|
||||
return func() {
|
||||
_ = s.index.Delete(s.getInMemoryServiceUser())
|
||||
}
|
||||
}
|
||||
}
|
||||
return func() {}
|
||||
}
|
||||
|
||||
func (s Service) getInMemoryServiceUser() accountsmsg.Account {
|
||||
return accountsmsg.Account{
|
||||
AccountEnabled: true,
|
||||
Id: s.Config.ServiceUser.UUID,
|
||||
PreferredName: s.Config.ServiceUser.Username,
|
||||
OnPremisesSamAccountName: s.Config.ServiceUser.Username,
|
||||
DisplayName: s.Config.ServiceUser.Username,
|
||||
UidNumber: s.Config.ServiceUser.UID,
|
||||
GidNumber: s.Config.ServiceUser.GID,
|
||||
}
|
||||
}
|
||||
|
||||
// ListAccounts implements the AccountsServiceHandler interface
|
||||
// the query contains account properties
|
||||
func (s Service) ListAccounts(ctx context.Context, in *accountssvc.ListAccountsRequest, out *accountssvc.ListAccountsResponse) (err error) {
|
||||
@@ -145,8 +118,6 @@ func (s Service) ListAccounts(ctx context.Context, in *accountssvc.ListAccountsR
|
||||
}
|
||||
onlySelf := hasSelf && !hasManagement
|
||||
|
||||
teardownServiceUser := s.serviceUserToIndex()
|
||||
defer teardownServiceUser()
|
||||
match, authRequest := getAuthQueryMatch(in.Query)
|
||||
if authRequest {
|
||||
password := match[2]
|
||||
|
||||
@@ -86,9 +86,33 @@ func New(opts ...Option) (s *Service, err error) {
|
||||
if err = s.createDefaultGroups(cfg.DemoUsersAndGroups); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.serviceUserToIndex()
|
||||
return
|
||||
}
|
||||
|
||||
// serviceUserToIndex temporarily adds a service user to the index, which is supposed to be removed before the lock on the handler function is released
|
||||
func (s Service) serviceUserToIndex() {
|
||||
if s.Config.ServiceUser.Username != "" && s.Config.ServiceUser.UUID != "" {
|
||||
_, err := s.index.Add(s.getInMemoryServiceUser())
|
||||
if err != nil {
|
||||
s.log.Logger.Err(err).Msg("service user was configured but failed to be added to the index")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s Service) getInMemoryServiceUser() accountsmsg.Account {
|
||||
return accountsmsg.Account{
|
||||
AccountEnabled: true,
|
||||
Id: s.Config.ServiceUser.UUID,
|
||||
PreferredName: s.Config.ServiceUser.Username,
|
||||
OnPremisesSamAccountName: s.Config.ServiceUser.Username,
|
||||
DisplayName: s.Config.ServiceUser.Username,
|
||||
UidNumber: s.Config.ServiceUser.UID,
|
||||
GidNumber: s.Config.ServiceUser.GID,
|
||||
}
|
||||
}
|
||||
|
||||
func (s Service) buildIndex() (*indexer.Indexer, error) {
|
||||
var indexcfg *idxcfg.Config
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
Change: settings service now stores its data via metadata service
|
||||
|
||||
Instead of writing files to disk it will use metadata service to do so
|
||||
|
||||
https://github.com/owncloud/ocis/pull/3232
|
||||
@@ -46,6 +46,7 @@ func (m *Manager) List(ctx context.Context, roleIDs []string) []*settingsmsg.Bun
|
||||
res, err := m.roleService.ListRoles(ctx, request)
|
||||
if err != nil {
|
||||
m.logger.Debug().Err(err).Msg("failed to fetch roles by roleIDs")
|
||||
return nil
|
||||
}
|
||||
for _, role := range res.Bundles {
|
||||
m.cache.set(role.Id, role)
|
||||
|
||||
@@ -19,7 +19,10 @@ type Config struct {
|
||||
HTTP HTTP `ocisConfig:"http"`
|
||||
GRPC GRPC `ocisConfig:"grpc"`
|
||||
|
||||
DataPath string `ocisConfig:"data_path" env:"SETTINGS_DATA_PATH"`
|
||||
StoreType string `ocisConfig:"store_type" env:"SETTINGS_STORE_TYPE"`
|
||||
DataPath string `ocisConfig:"data_path" env:"SETTINGS_DATA_PATH"`
|
||||
Metadata Metadata `ocisConfig:"metadata_config"`
|
||||
|
||||
Asset Asset `ocisConfig:"asset"`
|
||||
TokenManager TokenManager `ocisConfig:"token_manager"`
|
||||
|
||||
@@ -30,3 +33,13 @@ type Config struct {
|
||||
type Asset struct {
|
||||
Path string `ocisConfig:"path" env:"SETTINGS_ASSET_PATH"`
|
||||
}
|
||||
|
||||
// Metadata configures the metadata store to use
|
||||
type Metadata struct {
|
||||
GatewayAddress string `ocisConfig:"gateway_addr" env:"STORAGE_GATEWAY_GRPC_ADDR"`
|
||||
StorageAddress string `ocisConfig:"storage_addr" env:"STORAGE_GRPC_ADDR"`
|
||||
|
||||
ServiceUserID string `ocisConfig:"service_user_id" env:"METADATA_SERVICE_USER_UUID"`
|
||||
ServiceUserIDP string `ocisConfig:"service_user_idp" env:"OCIS_URL;METADATA_SERVICE_USER_IDP"`
|
||||
MachineAuthAPIKey string `ocisConfig:"machine_auth_api_key" env:"OCIS_MACHINE_AUTH_API_KEY"`
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ func FullDefaultConfig() *config.Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default config
|
||||
func DefaultConfig() *config.Config {
|
||||
return &config.Config{
|
||||
Service: config.Service{
|
||||
@@ -44,13 +45,22 @@ func DefaultConfig() *config.Config {
|
||||
Addr: "127.0.0.1:9191",
|
||||
Namespace: "com.owncloud.api",
|
||||
},
|
||||
DataPath: path.Join(defaults.BaseDataPath(), "settings"),
|
||||
StoreType: "metadata", // use metadata or filesystem
|
||||
DataPath: path.Join(defaults.BaseDataPath(), "settings"),
|
||||
Asset: config.Asset{
|
||||
Path: "",
|
||||
},
|
||||
TokenManager: config.TokenManager{
|
||||
JWTSecret: "Pive-Fumkiu4",
|
||||
},
|
||||
|
||||
Metadata: config.Metadata{
|
||||
GatewayAddress: "127.0.0.1:9142",
|
||||
StorageAddress: "127.0.0.1:9215",
|
||||
ServiceUserID: "95cb8724-03b2-11eb-a0a6-c33ef8ef53ad",
|
||||
ServiceUserIDP: "https://localhost:9200",
|
||||
MachineAuthAPIKey: "change-me-please",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ import (
|
||||
settingssvc "github.com/owncloud/ocis/protogen/gen/ocis/services/settings/v0"
|
||||
"github.com/owncloud/ocis/settings/pkg/config"
|
||||
"github.com/owncloud/ocis/settings/pkg/settings"
|
||||
store "github.com/owncloud/ocis/settings/pkg/store/filesystem"
|
||||
filestore "github.com/owncloud/ocis/settings/pkg/store/filesystem"
|
||||
metastore "github.com/owncloud/ocis/settings/pkg/store/metadata"
|
||||
merrors "go-micro.dev/v4/errors"
|
||||
"go-micro.dev/v4/metadata"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
@@ -32,12 +33,21 @@ type Service struct {
|
||||
// NewService returns a service implementation for Service.
|
||||
func NewService(cfg *config.Config, logger log.Logger) Service {
|
||||
service := Service{
|
||||
id: "ocis-settings",
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
manager: store.New(cfg),
|
||||
id: "ocis-settings",
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
switch cfg.StoreType {
|
||||
default:
|
||||
fallthrough
|
||||
case "metadata":
|
||||
service.manager = metastore.New(cfg)
|
||||
case "filesystem":
|
||||
service.manager = filestore.New(cfg)
|
||||
// TODO: if we want to further support filesystem store it should use default permissions from store/defaults/defaults.go instead using this duplicate
|
||||
service.RegisterDefaultRoles()
|
||||
}
|
||||
service.RegisterDefaultRoles()
|
||||
return service
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
package defaults
|
||||
|
||||
import (
|
||||
settingsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/settings/v0"
|
||||
)
|
||||
|
||||
const (
|
||||
// BundleUUIDRoleAdmin represents the admin role
|
||||
BundleUUIDRoleAdmin = "71881883-1768-46bd-a24d-a356a2afdf7f"
|
||||
|
||||
// BundleUUIDRoleUser represents the user role.
|
||||
BundleUUIDRoleUser = "d7beeea8-8ff4-406b-8fb6-ab2dd81e6b11"
|
||||
|
||||
// BundleUUIDRoleGuest represents the guest role.
|
||||
BundleUUIDRoleGuest = "38071a68-456a-4553-846a-fa67bf5596cc"
|
||||
|
||||
// BundleUUIDRoleMetadata represents the metadata user role
|
||||
BundleUUIDRoleMetadata = "95cb8724-03b2-11eb-a0a6-c33ef8ef53ad"
|
||||
|
||||
// RoleManagementPermissionID is the hardcoded setting UUID for the role management permission
|
||||
RoleManagementPermissionID string = "a53e601e-571f-4f86-8fec-d4576ef49c62"
|
||||
// RoleManagementPermissionName is the hardcoded setting name for the role management permission
|
||||
RoleManagementPermissionName string = "role-management"
|
||||
|
||||
// SettingsManagementPermissionID is the hardcoded setting UUID for the settings management permission
|
||||
SettingsManagementPermissionID string = "79e13b30-3e22-11eb-bc51-0b9f0bad9a58"
|
||||
// SettingsManagementPermissionName is the hardcoded setting name for the settings management permission
|
||||
SettingsManagementPermissionName string = "settings-management"
|
||||
|
||||
// SetSpaceQuotaPermissionID is the hardcoded setting UUID for the set space quota permission
|
||||
SetSpaceQuotaPermissionID string = "4e6f9709-f9e7-44f1-95d4-b762d27b7896"
|
||||
// SetSpaceQuotaPermissionName is the hardcoded setting name for the set space quota permission
|
||||
SetSpaceQuotaPermissionName string = "set-space-quota"
|
||||
|
||||
// ListAllSpacesPermissionID is the hardcoded setting UUID for the list all spaces permission
|
||||
ListAllSpacesPermissionID string = "016f6ddd-9501-4a0a-8ebe-64a20ee8ec82"
|
||||
// ListAllSpacesPermissionName is the hardcoded setting name for the list all spaces permission
|
||||
ListAllSpacesPermissionName string = "list-all-spaces"
|
||||
|
||||
// CreateSpacePermissionID is the hardcoded setting UUID for the create space permission
|
||||
CreateSpacePermissionID string = "79e13b30-3e22-11eb-bc51-0b9f0bad9a58"
|
||||
// CreateSpacePermissionName is the hardcoded setting name for the create space permission
|
||||
CreateSpacePermissionName string = "create-space"
|
||||
|
||||
settingUUIDProfileLanguage = "aa8cfbe5-95d4-4f7e-a032-c3c01f5f062f"
|
||||
|
||||
// AccountManagementPermissionID is the hardcoded setting UUID for the account management permission
|
||||
AccountManagementPermissionID string = "8e587774-d929-4215-910b-a317b1e80f73"
|
||||
// AccountManagementPermissionName is the hardcoded setting name for the account management permission
|
||||
AccountManagementPermissionName string = "account-management"
|
||||
// GroupManagementPermissionID is the hardcoded setting UUID for the group management permission
|
||||
GroupManagementPermissionID string = "522adfbe-5908-45b4-b135-41979de73245"
|
||||
// GroupManagementPermissionName is the hardcoded setting name for the group management permission
|
||||
GroupManagementPermissionName string = "group-management"
|
||||
// SelfManagementPermissionID is the hardcoded setting UUID for the self management permission
|
||||
SelfManagementPermissionID string = "e03070e9-4362-4cc6-a872-1c7cb2eb2b8e"
|
||||
// SelfManagementPermissionName is the hardcoded setting name for the self management permission
|
||||
SelfManagementPermissionName string = "self-management"
|
||||
)
|
||||
|
||||
// GenerateBundlesDefaultRoles bootstraps the default roles.
|
||||
func GenerateBundlesDefaultRoles() []*settingsmsg.Bundle {
|
||||
return []*settingsmsg.Bundle{
|
||||
generateBundleAdminRole(),
|
||||
generateBundleUserRole(),
|
||||
generateBundleGuestRole(),
|
||||
generateBundleProfileRequest(),
|
||||
generateBundleMetadataRole(),
|
||||
}
|
||||
}
|
||||
|
||||
func generateBundleAdminRole() *settingsmsg.Bundle {
|
||||
return &settingsmsg.Bundle{
|
||||
Id: BundleUUIDRoleAdmin,
|
||||
Name: "admin",
|
||||
Type: settingsmsg.Bundle_TYPE_ROLE,
|
||||
Extension: "ocis-roles",
|
||||
DisplayName: "Admin",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SYSTEM,
|
||||
},
|
||||
Settings: []*settingsmsg.Setting{
|
||||
{
|
||||
Id: RoleManagementPermissionID,
|
||||
Name: RoleManagementPermissionName,
|
||||
DisplayName: "Role Management",
|
||||
Description: "This permission gives full access to everything that is related to role management.",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_USER,
|
||||
Id: "all",
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READWRITE,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: SettingsManagementPermissionID,
|
||||
Name: SettingsManagementPermissionName,
|
||||
DisplayName: "Settings Management",
|
||||
Description: "This permission gives full access to everything that is related to settings management.",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_USER,
|
||||
Id: "all",
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READWRITE,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: "7d81f103-0488-4853-bce5-98dcce36d649",
|
||||
Name: "language-readwrite",
|
||||
DisplayName: "Permission to read and set the language (anyone)",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SETTING,
|
||||
Id: settingUUIDProfileLanguage,
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READWRITE,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: AccountManagementPermissionID,
|
||||
Name: AccountManagementPermissionName,
|
||||
DisplayName: "Account Management",
|
||||
Description: "This permission gives full access to everything that is related to account management.",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_USER,
|
||||
Id: "all",
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READWRITE,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: GroupManagementPermissionID,
|
||||
Name: GroupManagementPermissionName,
|
||||
DisplayName: "Group Management",
|
||||
Description: "This permission gives full access to everything that is related to group management.",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_GROUP,
|
||||
Id: "all",
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READWRITE,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: SetSpaceQuotaPermissionID,
|
||||
Name: SetSpaceQuotaPermissionName,
|
||||
DisplayName: "Set Space Quota",
|
||||
Description: "This permission allows to manage space quotas.",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SYSTEM,
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READWRITE,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: CreateSpacePermissionID,
|
||||
Name: CreateSpacePermissionName,
|
||||
DisplayName: "Create Space",
|
||||
Description: "This permission allows to create new spaces.",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SYSTEM,
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READWRITE,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: ListAllSpacesPermissionID,
|
||||
Name: ListAllSpacesPermissionName,
|
||||
DisplayName: "List All Spaces",
|
||||
Description: "This permission allows list all spaces.",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SYSTEM,
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READ,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_ALL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func generateBundleUserRole() *settingsmsg.Bundle {
|
||||
return &settingsmsg.Bundle{
|
||||
Id: BundleUUIDRoleUser,
|
||||
Name: "user",
|
||||
Type: settingsmsg.Bundle_TYPE_ROLE,
|
||||
Extension: "ocis-roles",
|
||||
DisplayName: "User",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SYSTEM,
|
||||
},
|
||||
Settings: []*settingsmsg.Setting{
|
||||
{
|
||||
Id: "640e00d2-4df8-41bd-b1c2-9f30a01e0e99",
|
||||
Name: "language-readwrite",
|
||||
DisplayName: "Permission to read and set the language (self)",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SETTING,
|
||||
Id: settingUUIDProfileLanguage,
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READWRITE,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_OWN,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: SelfManagementPermissionID,
|
||||
Name: SelfManagementPermissionName,
|
||||
DisplayName: "Self Management",
|
||||
Description: "This permission gives access to self management.",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_USER,
|
||||
Id: "me",
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READWRITE,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_OWN,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: CreateSpacePermissionID,
|
||||
Name: CreateSpacePermissionName,
|
||||
DisplayName: "Create own Space",
|
||||
Description: "This permission allows to create a space owned by the current user.",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SYSTEM, // TODO resource type space? self? me? own?
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_CREATE,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_OWN,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func generateBundleGuestRole() *settingsmsg.Bundle {
|
||||
return &settingsmsg.Bundle{
|
||||
Id: BundleUUIDRoleGuest,
|
||||
Name: "guest",
|
||||
Type: settingsmsg.Bundle_TYPE_ROLE,
|
||||
Extension: "ocis-roles",
|
||||
DisplayName: "Guest",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SYSTEM,
|
||||
},
|
||||
Settings: []*settingsmsg.Setting{
|
||||
{
|
||||
Id: "ca878636-8b1a-4fae-8282-8617a4c13597",
|
||||
Name: "language-readwrite",
|
||||
DisplayName: "Permission to read and set the language (self)",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SETTING,
|
||||
Id: settingUUIDProfileLanguage,
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READWRITE,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_OWN,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func generateBundleProfileRequest() *settingsmsg.Bundle {
|
||||
return &settingsmsg.Bundle{
|
||||
Id: "2a506de7-99bd-4f0d-994e-c38e72c28fd9",
|
||||
Name: "profile",
|
||||
Extension: "ocis-accounts",
|
||||
Type: settingsmsg.Bundle_TYPE_DEFAULT,
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SYSTEM,
|
||||
},
|
||||
DisplayName: "Profile",
|
||||
Settings: []*settingsmsg.Setting{
|
||||
{
|
||||
Id: settingUUIDProfileLanguage,
|
||||
Name: "language",
|
||||
DisplayName: "Language",
|
||||
Description: "User language",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_USER,
|
||||
},
|
||||
Value: &languageSetting,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func generateBundleMetadataRole() *settingsmsg.Bundle {
|
||||
return &settingsmsg.Bundle{
|
||||
Id: BundleUUIDRoleMetadata,
|
||||
Name: "metadata",
|
||||
Type: settingsmsg.Bundle_TYPE_ROLE,
|
||||
Extension: "ocis-roles",
|
||||
DisplayName: "Metadata",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SYSTEM,
|
||||
},
|
||||
Settings: []*settingsmsg.Setting{
|
||||
{
|
||||
Id: CreateSpacePermissionID,
|
||||
Name: CreateSpacePermissionName,
|
||||
DisplayName: "Create own Space",
|
||||
Description: "This permission allows to create a space owned by the current user.",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SYSTEM, // TODO resource type space? self? me? own?
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_CREATE,
|
||||
Constraint: settingsmsg.Permission_CONSTRAINT_OWN,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: languageSetting needed?
|
||||
var languageSetting = settingsmsg.Setting_SingleChoiceValue{
|
||||
SingleChoiceValue: &settingsmsg.SingleChoiceList{
|
||||
Options: []*settingsmsg.ListOption{
|
||||
{
|
||||
Value: &settingsmsg.ListOptionValue{
|
||||
Option: &settingsmsg.ListOptionValue_StringValue{
|
||||
StringValue: "cs",
|
||||
},
|
||||
},
|
||||
DisplayValue: "Czech",
|
||||
},
|
||||
{
|
||||
Value: &settingsmsg.ListOptionValue{
|
||||
Option: &settingsmsg.ListOptionValue_StringValue{
|
||||
StringValue: "de",
|
||||
},
|
||||
},
|
||||
DisplayValue: "Deutsch",
|
||||
},
|
||||
{
|
||||
Value: &settingsmsg.ListOptionValue{
|
||||
Option: &settingsmsg.ListOptionValue_StringValue{
|
||||
StringValue: "en",
|
||||
},
|
||||
},
|
||||
DisplayValue: "English",
|
||||
Default: true,
|
||||
},
|
||||
{
|
||||
Value: &settingsmsg.ListOptionValue{
|
||||
Option: &settingsmsg.ListOptionValue_StringValue{
|
||||
StringValue: "es",
|
||||
},
|
||||
},
|
||||
DisplayValue: "Español",
|
||||
},
|
||||
{
|
||||
Value: &settingsmsg.ListOptionValue{
|
||||
Option: &settingsmsg.ListOptionValue_StringValue{
|
||||
StringValue: "fr",
|
||||
},
|
||||
},
|
||||
DisplayValue: "Français",
|
||||
},
|
||||
{
|
||||
Value: &settingsmsg.ListOptionValue{
|
||||
Option: &settingsmsg.ListOptionValue_StringValue{
|
||||
StringValue: "gl",
|
||||
},
|
||||
},
|
||||
DisplayValue: "Galego",
|
||||
},
|
||||
{
|
||||
Value: &settingsmsg.ListOptionValue{
|
||||
Option: &settingsmsg.ListOptionValue_StringValue{
|
||||
StringValue: "it",
|
||||
},
|
||||
},
|
||||
DisplayValue: "Italiano",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// DefaultRoleAssignments returns (as one might guess) the default role assignments
|
||||
func DefaultRoleAssignments() []*settingsmsg.UserRoleAssignment {
|
||||
return []*settingsmsg.UserRoleAssignment{
|
||||
// accounts service user for the metadata user is allowed to create spaces
|
||||
{
|
||||
AccountUuid: "95cb8724-03b2-11eb-a0a6-c33ef8ef53ad",
|
||||
RoleId: BundleUUIDRoleAdmin,
|
||||
},
|
||||
// default admin users
|
||||
{
|
||||
AccountUuid: "058bff95-6708-4fe5-91e4-9ea3d377588b",
|
||||
RoleId: BundleUUIDRoleAdmin,
|
||||
}, {
|
||||
AccountUuid: "ddc2004c-0977-11eb-9d3f-a793888cd0f8",
|
||||
RoleId: BundleUUIDRoleAdmin,
|
||||
}, {
|
||||
AccountUuid: "820ba2a1-3f54-4538-80a4-2d73007e30bf",
|
||||
RoleId: BundleUUIDRoleAdmin,
|
||||
}, {
|
||||
AccountUuid: "bc596f3c-c955-4328-80a0-60d018b4ad57",
|
||||
RoleId: BundleUUIDRoleAdmin,
|
||||
},
|
||||
// default users with role "user"
|
||||
{
|
||||
AccountUuid: "4c510ada-c86b-4815-8820-42cdf82c3d51",
|
||||
RoleId: BundleUUIDRoleUser,
|
||||
}, {
|
||||
AccountUuid: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c",
|
||||
RoleId: BundleUUIDRoleUser,
|
||||
}, {
|
||||
AccountUuid: "932b4540-8d16-481e-8ef4-588e4b6b151c",
|
||||
RoleId: BundleUUIDRoleUser,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Package store implements the go-micro store interface
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
settingsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/settings/v0"
|
||||
"github.com/owncloud/ocis/settings/pkg/store/defaults"
|
||||
)
|
||||
|
||||
// ListRoleAssignments loads and returns all role assignments matching the given assignment identifier.
|
||||
func (s *Store) ListRoleAssignments(accountUUID string) ([]*settingsmsg.UserRoleAssignment, error) {
|
||||
if s.mdc == nil || accountUUID == "95cb8724-03b2-11eb-a0a6-c33ef8ef53ad" {
|
||||
return defaultRoleAssignments(accountUUID), nil
|
||||
}
|
||||
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 defaultRoleAssignments(accID string) []*settingsmsg.UserRoleAssignment {
|
||||
var assmnts []*settingsmsg.UserRoleAssignment
|
||||
for _, r := range defaults.DefaultRoleAssignments() {
|
||||
if r.AccountUuid == accID {
|
||||
assmnts = append(assmnts, r)
|
||||
}
|
||||
}
|
||||
return assmnts
|
||||
}
|
||||
|
||||
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,189 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
olog "github.com/owncloud/ocis/ocis-pkg/log"
|
||||
settingsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/settings/v0"
|
||||
"github.com/owncloud/ocis/settings/pkg/config/defaults"
|
||||
"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{},
|
||||
cfg: defaults.DefaultConfig(),
|
||||
}
|
||||
|
||||
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() {
|
||||
_ = 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"
|
||||
settingsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/settings/v0"
|
||||
"github.com/owncloud/ocis/settings/pkg/store/defaults"
|
||||
)
|
||||
|
||||
// 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/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 (
|
||||
settingsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/settings/v0"
|
||||
"github.com/owncloud/ocis/settings/pkg/settings"
|
||||
"github.com/owncloud/ocis/settings/pkg/util"
|
||||
)
|
||||
|
||||
// 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/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"
|
||||
olog "github.com/owncloud/ocis/ocis-pkg/log"
|
||||
settingsmsg "github.com/owncloud/ocis/protogen/gen/ocis/messages/settings/v0"
|
||||
"github.com/owncloud/ocis/settings/pkg/config"
|
||||
"github.com/owncloud/ocis/settings/pkg/settings"
|
||||
"github.com/owncloud/ocis/settings/pkg/store/defaults"
|
||||
)
|
||||
|
||||
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.ServiceUserID, cfg.ServiceUserIDP, cfg.MachineAuthAPIKey)
|
||||
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() {
|
||||
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/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/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/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)
|
||||
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user