From 1aebc456b08ac33c77927170706e74df273432d9 Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Wed, 30 Oct 2019 18:00:40 +0100 Subject: [PATCH] Add improve settings for messaging --- messaging/importer/importer.go | 50 +++++++++++------ messaging/provision-config.go | 64 +++++++++++++++++++++- messaging/service/service.go | 6 +- messaging/service/settings.go | 58 ++++++++++++++++++-- messaging/types/settings.go | 43 +++++++++++++++ provision/messaging/src/0100_settings.yaml | 18 +++--- provision/messaging/static.go | 2 +- 7 files changed, 204 insertions(+), 37 deletions(-) create mode 100644 messaging/types/settings.go diff --git a/messaging/importer/importer.go b/messaging/importer/importer.go index c426137ea..3f92f04b8 100644 --- a/messaging/importer/importer.go +++ b/messaging/importer/importer.go @@ -37,15 +37,23 @@ func (imp *Importer) Cast(in interface{}) (err error) { return deinterfacer.Each(in, func(index int, key string, val interface{}) (err error) { switch key { case "channels": - return imp.channels.CastSet(val) + if imp.channels != nil { + return imp.channels.CastSet(val) + } case "channel": - return imp.channels.CastSet([]interface{}{val}) + if imp.channels != nil { + return imp.channels.CastSet([]interface{}{val}) + } case "settings": - return imp.settings.CastSet(val) + if imp.settings != nil { + return imp.settings.CastSet(val) + } case "allow", "deny": - return imp.permissions.CastResourcesSet(key, val) + if imp.permissions != nil { + return imp.permissions.CastResourcesSet(key, val) + } default: err = fmt.Errorf("unexpected key %q", key) @@ -56,25 +64,31 @@ func (imp *Importer) Cast(in interface{}) (err error) { } func (imp *Importer) Store(ctx context.Context, rk channelKeeper, pk permissions.ImportKeeper, sk settings.ImportKeeper, roles sysTypes.RoleSet) (err error) { - err = imp.channels.Store(ctx, rk) - if err != nil { - return + if imp.channels != nil { + err = imp.channels.Store(ctx, rk) + if err != nil { + return + } } - // Make sure we properly replace channel handles with IDs - roles.Walk(func(r *sysTypes.Role) error { - imp.permissions.UpdateRoles(r.Handle, r.ID) - return nil - }) + if imp.permissions != nil { + // Make sure we properly replace channel handles with IDs + roles.Walk(func(r *sysTypes.Role) error { + imp.permissions.UpdateRoles(r.Handle, r.ID) + return nil + }) - err = imp.permissions.Store(ctx, pk) - if err != nil { - return + err = imp.permissions.Store(ctx, pk) + if err != nil { + return + } } - err = imp.settings.Store(ctx, sk) - if err != nil { - return + if imp.settings != nil { + err = imp.settings.Store(ctx, sk) + if err != nil { + return + } } return nil diff --git a/messaging/provision-config.go b/messaging/provision-config.go index 467976c14..4b57639fc 100644 --- a/messaging/provision-config.go +++ b/messaging/provision-config.go @@ -2,9 +2,11 @@ package messaging import ( "context" + "io" "github.com/pkg/errors" "github.com/spf13/cobra" + "gopkg.in/yaml.v2" "github.com/cortezaproject/corteza-server/messaging/importer" "github.com/cortezaproject/corteza-server/messaging/service" @@ -12,21 +14,23 @@ import ( "github.com/cortezaproject/corteza-server/pkg/auth" "github.com/cortezaproject/corteza-server/pkg/cli" impAux "github.com/cortezaproject/corteza-server/pkg/importer" + "github.com/cortezaproject/corteza-server/pkg/settings" provision "github.com/cortezaproject/corteza-server/provision/messaging" ) -func provisionConfig(ctx context.Context, cmd *cobra.Command, c *cli.Config) error { +func provisionConfig(ctx context.Context, cmd *cobra.Command, c *cli.Config) (err error) { c.Log.Debug("running configuration provision") c.InitServices(ctx, c) + var provisioned bool + // Make sure we have all full access for provisioning ctx = auth.SetSuperUserContext(ctx) - if provisioned, err := isProvisioned(ctx); err != nil { + if provisioned, err = isProvisioned(ctx); err != nil { return err } else if provisioned { c.Log.Debug("configuration already provisioned") - return nil } readers, err := impAux.ReadStatic(provision.Asset) @@ -34,6 +38,10 @@ func provisionConfig(ctx context.Context, cmd *cobra.Command, c *cli.Config) err return err } + if provisioned { + return partialImportSettings(ctx, service.DefaultSettings, readers...) + } + return errors.Wrap( importer.Import(ctx, readers...), "could not provision configuration for messaging service", @@ -45,3 +53,53 @@ func isProvisioned(ctx context.Context) (bool, error) { cc, _, err := service.DefaultChannel.With(ctx).Find(types.ChannelFilter{IncludeDeleted: true}) return len(cc) > 0, err } + +// Partial import of settings from provision files +func partialImportSettings(ctx context.Context, ss service.SettingsService, ff ...io.Reader) (err error) { + var ( + // decoded content from YAML files + aux interface{} + + si = settings.NewImporter() + + // importer w/o permissions & roles + // we need only settings + imp = importer.NewImporter(nil, si, nil) + + // current value + current settings.ValueSet + + // unexisting values + unex settings.ValueSet + ) + + for _, f := range ff { + if err = yaml.NewDecoder(f).Decode(&aux); err != nil { + return + } + + err = imp.Cast(aux) + if err != nil { + return + } + } + + ss = ss.With(ctx) + + // Get all "current" settings storage + current, err = ss.FindByPrefix("") + if err != nil { + return + } + + // Compare current settings with imported, get all that do not exist yet + if unex = si.GetValues(); len(unex) > 0 { + // Store non existing + err = ss.BulkSet(current.New(unex)) + if err != nil { + return + } + } + + return nil +} diff --git a/messaging/service/service.go b/messaging/service/service.go index 7c0805161..2846cde63 100644 --- a/messaging/service/service.go +++ b/messaging/service/service.go @@ -7,6 +7,7 @@ import ( "go.uber.org/zap" "github.com/cortezaproject/corteza-server/messaging/repository" + "github.com/cortezaproject/corteza-server/messaging/types" "github.com/cortezaproject/corteza-server/pkg/cli/options" "github.com/cortezaproject/corteza-server/pkg/http" "github.com/cortezaproject/corteza-server/pkg/permissions" @@ -41,6 +42,9 @@ var ( DefaultSettings SettingsService DefaultAccessControl *accessControl + // CurrentSettings represents current messaging settings + CurrentSettings = &types.Settings{} + DefaultAttachment AttachmentService DefaultChannel ChannelService DefaultMessage MessageService @@ -101,7 +105,7 @@ func Init(ctx context.Context, log *zap.Logger, c Config) (err error) { } DefaultAccessControl = AccessControl(DefaultPermissions) - DefaultSettings = Settings(ctx, DefaultInternalSettings) + DefaultSettings = Settings(ctx, DefaultInternalSettings, CurrentSettings) DefaultEvent = Event(ctx) DefaultChannel = Channel(ctx) diff --git a/messaging/service/settings.go b/messaging/service/settings.go index fe16dcc15..dfbc069c4 100644 --- a/messaging/service/settings.go +++ b/messaging/service/settings.go @@ -9,6 +9,7 @@ import ( "go.uber.org/zap/zapcore" "github.com/cortezaproject/corteza-server/messaging/repository" + "github.com/cortezaproject/corteza-server/messaging/types" "github.com/cortezaproject/corteza-server/pkg/logger" internalSettings "github.com/cortezaproject/corteza-server/pkg/settings" ) @@ -22,6 +23,7 @@ type ( ac settingsAccessController internalSettings internalSettings.Service + current *types.Settings } settingsAccessController interface { @@ -30,7 +32,7 @@ type ( } SettingsService interface { - With(ctx context.Context) SettingsService + With(ctx context.Context) *settings FindByPrefix(prefix string) (vv internalSettings.ValueSet, err error) Set(v *internalSettings.Value) (err error) BulkSet(vv internalSettings.ValueSet) (err error) @@ -38,15 +40,16 @@ type ( } ) -func Settings(ctx context.Context, intSet internalSettings.Service) SettingsService { +func Settings(ctx context.Context, intSet internalSettings.Service, current *types.Settings) *settings { return (&settings{ internalSettings: intSet, ac: DefaultAccessControl, logger: DefaultLogger.Named("settings"), + current: current, }).With(ctx) } -func (svc settings) With(ctx context.Context) SettingsService { +func (svc settings) With(ctx context.Context) *settings { db := repository.DB(ctx) return &settings{ @@ -56,6 +59,8 @@ func (svc settings) With(ctx context.Context) SettingsService { logger: svc.logger, internalSettings: svc.internalSettings.With(ctx), + + current: svc.current, } } @@ -71,12 +76,28 @@ func (svc settings) FindByPrefix(prefix string) (vv internalSettings.ValueSet, e return svc.internalSettings.FindByPrefix(prefix) } +// UpdateCurrent loads settings values from storage and updates current settings variable +// +// It accesses internal settings directly because +// we do not want any security checks for this +func (svc settings) UpdateCurrent() error { + if vv, err := svc.internalSettings.FindByPrefix(""); err != nil { + return err + } else { + return svc.updateCurrent(vv.KV()) + } +} + func (svc settings) Set(v *internalSettings.Value) (err error) { if !svc.ac.CanManageSettings(svc.ctx) { return errors.New("not allowed to manage settings") } - return svc.internalSettings.Set(v) + if err = svc.internalSettings.Set(v); err != nil { + return + } + + return svc.updateCurrent(internalSettings.KV{v.Name: v.Value}) } func (svc settings) BulkSet(vv internalSettings.ValueSet) (err error) { @@ -84,7 +105,34 @@ func (svc settings) BulkSet(vv internalSettings.ValueSet) (err error) { return errors.New("not allowed to manage settings") } - return svc.internalSettings.BulkSet(vv) + var old internalSettings.ValueSet + if old, err = svc.internalSettings.FindByPrefix(""); err != nil { + return + } else { + vv = old.Changed(vv) + } + + if err = svc.internalSettings.BulkSet(vv); err != nil { + return + } + + for _, v := range vv { + svc.log(svc.ctx, + zap.String("name", v.Name), + zap.Stringer("value", v.Value)).Info("settings changed") + } + + return svc.updateCurrent(vv.KV()) +} + +func (svc settings) updateCurrent(kv internalSettings.KV) (err error) { + // update current settings with new values + if err = kv.Decode(svc.current); err != nil { + return + } + + svc.log(svc.ctx).Info("current settings updated") + return } func (svc settings) Get(name string, ownedBy uint64) (out *internalSettings.Value, err error) { diff --git a/messaging/types/settings.go b/messaging/types/settings.go new file mode 100644 index 000000000..dc25573f5 --- /dev/null +++ b/messaging/types/settings.go @@ -0,0 +1,43 @@ +package types + +type ( + Settings struct { + // UI related settings + UI struct { + // Emoji + // @todo implementation + Emoji struct { + Enabled bool + } + + // In-browser notifications + // @todo implementation + BrowserNotifications struct { + Enabled bool + Header string + MessageTrim uint `kv:"message-trim"` + } `kv:"browser-notifications"` + } `kv:"ui"` + + // Message related settings + Message struct { + // @todo implementation + Attachments struct { + // Completely disable attachments + Enabled bool + + // What is max size (in MB, so: MaxSize x 2^20) + MaxSize uint `kv:"max-size"` + + // List of mime-types we support, + Mimetypes []string + + // Enable/disable individual attachment sources (mobile) + Source struct { + Gallery struct{ Enabled bool } + Camera struct{ Enabled bool } + } + } + } + } +) diff --git a/provision/messaging/src/0100_settings.yaml b/provision/messaging/src/0100_settings.yaml index 25f49befb..932da5081 100644 --- a/provision/messaging/src/0100_settings.yaml +++ b/provision/messaging/src/0100_settings.yaml @@ -1,10 +1,10 @@ settings: - emoji.enabled: true - message.attachment.enabled: true - message.attachment.source.gallery.enabled: true - message.attachment.source.camera.enabled: true - message.attachment.max-size: 10 - message.attachment.type.whitelist: [] - notification.enabled: true - notification.header.template: ${user} in ${channel} | Corteza - notification.message.max-length: 200 + ui.emoji.enabled: true + ui.browser-notifications.enabled: true + ui.browser-notifications.header: ${user} in ${channel} + ui.browser-notifications.message-trim: 200 + message.attachments.enabled: true + message.attachments.max-size: 10 + message.attachments.mimetypes: [] + message.attachments.source.gallery.enabled: true + message.attachments.source.camera.enabled: true diff --git a/provision/messaging/static.go b/provision/messaging/static.go index 80985a030..6f535089a 100644 --- a/provision/messaging/static.go +++ b/provision/messaging/static.go @@ -3,4 +3,4 @@ // Package contains static assets. package messaging -var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00 \x000000_access_control.yamlUT\x05\x00\x01\x80Cm8allow:\n everyone:\n messaging:\n - access\n\n admins:\n messaging:\n - access\n - grant\n - settings.read\n - settings.manage\n - channel.public.create\n - channel.private.create\n - channel.group.create\n\n messaging:channel:\n - update\n - leave\n - read\n - join\n - delete\n - undelete\n - archive\n - unarchive\n - members.manage\n - attachments.manage\n - message.attach\n - message.update.all\n - message.update.own\n - message.delete.all\n - message.delete.own\n - message.embed\n - message.send\n - message.reply\n - message.react\n\nPK\x07\x08\xae\x83\x83d\x8e\x02\x00\x00\x8e\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00 \x000100_settings.yamlUT\x05\x00\x01\x80Cm8settings:\n emoji.enabled: true\n message.attachment.enabled: true\n message.attachment.source.gallery.enabled: true\n message.attachment.source.camera.enabled: true\n message.attachment.max-size: 10\n message.attachment.type.whitelist: []\n notification.enabled: true\n notification.header.template: ${user} in ${channel} | Corteza\n notification.message.max-length: 200\nPK\x07\x08\xc2\x0c\xfdkt\x01\x00\x00t\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00 \x001000_channels.yamlUT\x05\x00\x01\x80Cm8channels:\n - name: General\n type: public\n - name: Random\n type: public\nPK\x07\x08\xe8\x83F\xf8O\x00\x00\x00O\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae\x83\x83d\x8e\x02\x00\x00\x8e\x02\x00\x00\x18\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x000000_access_control.yamlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xc2\x0c\xfdkt\x01\x00\x00t\x01\x00\x00\x12\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xdd\x02\x00\x000100_settings.yamlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xe8\x83F\xf8O\x00\x00\x00O\x00\x00\x00\x12\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x9a\x04\x00\x001000_channels.yamlUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x03\x00\x03\x00\xe1\x00\x00\x002\x05\x00\x00\x00\x00" +var Asset = "PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x18\x00 \x000000_access_control.yamlUT\x05\x00\x01\x80Cm8allow:\n everyone:\n messaging:\n - access\n\n admins:\n messaging:\n - access\n - grant\n - settings.read\n - settings.manage\n - channel.public.create\n - channel.private.create\n - channel.group.create\n\n messaging:channel:\n - update\n - leave\n - read\n - join\n - delete\n - undelete\n - archive\n - unarchive\n - members.manage\n - attachments.manage\n - message.attach\n - message.update.all\n - message.update.own\n - message.delete.all\n - message.delete.own\n - message.embed\n - message.send\n - message.reply\n - message.react\n\nPK\x07\x08\xae\x83\x83d\x8e\x02\x00\x00\x8e\x02\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00 \x000100_settings.yamlUT\x05\x00\x01\x80Cm8settings:\n ui.emoji.enabled: true\n ui.browser-notifications.enabled: true\n ui.browser-notifications.header: ${user} in ${channel}\n ui.browser-notifications.message-trim: 200\n message.attachments.enabled: true\n message.attachments.max-size: 10\n message.attachments.mimetypes: []\n message.attachments.source.gallery.enabled: true\n message.attachments.source.camera.enabled: true\nPK\x07\x08Cy\xf0y\x82\x01\x00\x00\x82\x01\x00\x00PK\x03\x04\x14\x00\x08\x00\x00\x00\x00\x00!(\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x12\x00 \x001000_channels.yamlUT\x05\x00\x01\x80Cm8channels:\n - name: General\n type: public\n - name: Random\n type: public\nPK\x07\x08\xe8\x83F\xf8O\x00\x00\x00O\x00\x00\x00PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xae\x83\x83d\x8e\x02\x00\x00\x8e\x02\x00\x00\x18\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\x00\x00\x00\x000000_access_control.yamlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(Cy\xf0y\x82\x01\x00\x00\x82\x01\x00\x00\x12\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xdd\x02\x00\x000100_settings.yamlUT\x05\x00\x01\x80Cm8PK\x01\x02\x14\x03\x14\x00\x08\x00\x00\x00\x00\x00!(\xe8\x83F\xf8O\x00\x00\x00O\x00\x00\x00\x12\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x81\xa8\x04\x00\x001000_channels.yamlUT\x05\x00\x01\x80Cm8PK\x05\x06\x00\x00\x00\x00\x03\x00\x03\x00\xe1\x00\x00\x00@\x05\x00\x00\x00\x00"