From 12fd426ec76aec614edb82b0e6c106887f1e3e5e Mon Sep 17 00:00:00 2001 From: Vivek Patel Date: Thu, 23 Jun 2022 23:51:36 +0530 Subject: [PATCH] Add validation for password while updating system settings Also, adds setting actions file and updated all settings errors to translated error --- system/service/service.go | 2 +- system/service/setting_actions.gen.go | 498 ++++++++++++++++++++++++++ system/service/setting_actions.yaml | 52 +++ system/service/settings.go | 77 +++- system/types/app_settings.go | 10 +- tests/system/settings_test.go | 65 +++- 6 files changed, 683 insertions(+), 21 deletions(-) create mode 100644 system/service/setting_actions.gen.go create mode 100644 system/service/setting_actions.yaml diff --git a/system/service/service.go b/system/service/service.go index aeadc07ae..cdbaa0a0a 100644 --- a/system/service/service.go +++ b/system/service/service.go @@ -150,7 +150,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock DefaultAccessControl = AccessControl(s) - DefaultSettings = Settings(ctx, DefaultStore, DefaultLogger, DefaultAccessControl, CurrentSettings) + DefaultSettings = Settings(ctx, DefaultStore, DefaultLogger, DefaultAccessControl, DefaultActionlog, CurrentSettings) DefaultDalConnection = Connection(ctx, dal.Service(), c.DB) diff --git a/system/service/setting_actions.gen.go b/system/service/setting_actions.gen.go new file mode 100644 index 000000000..4eaec689e --- /dev/null +++ b/system/service/setting_actions.gen.go @@ -0,0 +1,498 @@ +package service + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// system/service/setting_actions.yaml + +import ( + "context" + "fmt" + "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/pkg/errors" + "github.com/cortezaproject/corteza-server/pkg/locale" + "github.com/cortezaproject/corteza-server/system/types" + "strings" + "time" +) + +type ( + settingsActionProps struct { + settings *types.SettingValue + } + + settingsAction struct { + timestamp time.Time + resource string + action string + log string + severity actionlog.Severity + + // prefix for error when action fails + errorMessage string + + props *settingsActionProps + } + + settingsLogMetaKey struct{} + settingsPropsMetaKey struct{} +) + +var ( + // just a placeholder to cover template cases w/o fmt package use + _ = fmt.Println +) + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Props methods +// setSettings updates settingsActionProps's settings +// +// Allows method chaining +// +// This function is auto-generated. +// +func (p *settingsActionProps) setSettings(settings *types.SettingValue) *settingsActionProps { + p.settings = settings + return p +} + +// Serialize converts settingsActionProps to actionlog.Meta +// +// This function is auto-generated. +// +func (p settingsActionProps) Serialize() actionlog.Meta { + var ( + m = make(actionlog.Meta) + ) + + if p.settings != nil { + m.Set("settings.name", p.settings.Name, true) + m.Set("settings.value", p.settings.Value, true) + } + + return m +} + +// tr translates string and replaces meta value placeholder with values +// +// This function is auto-generated. +// +func (p settingsActionProps) Format(in string, err error) string { + var ( + pairs = []string{"{{err}}"} + // first non-empty string + fns = func(ii ...interface{}) string { + for _, i := range ii { + if s := fmt.Sprintf("%v", i); len(s) > 0 { + return s + } + } + + return "" + } + ) + + if err != nil { + pairs = append(pairs, err.Error()) + } else { + pairs = append(pairs, "nil") + } + + if p.settings != nil { + // replacement for "{{settings}}" (in order how fields are defined) + pairs = append( + pairs, + "{{settings}}", + fns( + p.settings.Name, + p.settings.Value, + ), + ) + pairs = append(pairs, "{{settings.name}}", fns(p.settings.Name)) + pairs = append(pairs, "{{settings.value}}", fns(p.settings.Value)) + } + return strings.NewReplacer(pairs...).Replace(in) +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Action methods + +// String returns loggable description as string +// +// This function is auto-generated. +// +func (a *settingsAction) String() string { + var props = &settingsActionProps{} + + if a.props != nil { + props = a.props + } + + return props.Format(a.log, nil) +} + +func (e *settingsAction) ToAction() *actionlog.Action { + return &actionlog.Action{ + Resource: e.resource, + Action: e.action, + Severity: e.severity, + Description: e.String(), + Meta: e.props.Serialize(), + } +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Action constructors + +// SettingsActionLookup returns "system:setting.lookup" action +// +// This function is auto-generated. +// +func SettingsActionLookup(props ...*settingsActionProps) *settingsAction { + a := &settingsAction{ + timestamp: time.Now(), + resource: "system:setting", + action: "lookup", + log: "looked-up for a setting", + severity: actionlog.Info, + } + + if len(props) > 0 { + a.props = props[0] + } + + return a +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* +// Error constructors + +// SettingsErrGeneric returns "system:setting.generic" as *errors.Error +// +// +// This function is auto-generated. +// +func SettingsErrGeneric(mm ...*settingsActionProps) *errors.Error { + var p = &settingsActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("failed to complete request due to internal error", nil), + + errors.Meta("type", "generic"), + errors.Meta("resource", "system:setting"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(settingsLogMetaKey{}, "{err}"), + errors.Meta(settingsPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "settings.errors.generic"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// SettingsErrNotAllowedToRead returns "system:setting.notAllowedToRead" as *errors.Error +// +// +// This function is auto-generated. +// +func SettingsErrNotAllowedToRead(mm ...*settingsActionProps) *errors.Error { + var p = &settingsActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to read this setting", nil), + + errors.Meta("type", "notAllowedToRead"), + errors.Meta("resource", "system:setting"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(settingsLogMetaKey{}, "failed to read {{settings.name}}; insufficient permissions"), + errors.Meta(settingsPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "settings.errors.notAllowedToRead"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// SettingsErrNotAllowedToManage returns "system:setting.notAllowedToManage" as *errors.Error +// +// +// This function is auto-generated. +// +func SettingsErrNotAllowedToManage(mm ...*settingsActionProps) *errors.Error { + var p = &settingsActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to manage this setting", nil), + + errors.Meta("type", "notAllowedToManage"), + errors.Meta("resource", "system:setting"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(settingsLogMetaKey{}, "failed to manage {{settings.name}}; insufficient permissions"), + errors.Meta(settingsPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "settings.errors.notAllowedToManage"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// SettingsErrInvalidPasswordMinLength returns "system:setting.invalidPasswordMinLength" as *errors.Error +// +// +// This function is auto-generated. +// +func SettingsErrInvalidPasswordMinLength(mm ...*settingsActionProps) *errors.Error { + var p = &settingsActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("password constraint minimum length should be at least 8 characters", nil), + + errors.Meta("type", "invalidPasswordMinLength"), + errors.Meta("resource", "system:setting"), + + errors.Meta(settingsPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "settings.errors.invalidPasswordMinLength"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// SettingsErrInvalidPasswordMinUpperCase returns "system:setting.invalidPasswordMinUpperCase" as *errors.Error +// +// +// This function is auto-generated. +// +func SettingsErrInvalidPasswordMinUpperCase(mm ...*settingsActionProps) *errors.Error { + var p = &settingsActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("password constraint minimum upper case count should not be a negative number", nil), + + errors.Meta("type", "invalidPasswordMinUpperCase"), + errors.Meta("resource", "system:setting"), + + errors.Meta(settingsPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "settings.errors.invalidPasswordMinUpperCase"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// SettingsErrInvalidPasswordMinLowerCase returns "system:setting.invalidPasswordMinLowerCase" as *errors.Error +// +// +// This function is auto-generated. +// +func SettingsErrInvalidPasswordMinLowerCase(mm ...*settingsActionProps) *errors.Error { + var p = &settingsActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("password constraint minimum lower case count should not be a negative number", nil), + + errors.Meta("type", "invalidPasswordMinLowerCase"), + errors.Meta("resource", "system:setting"), + + errors.Meta(settingsPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "settings.errors.invalidPasswordMinLowerCase"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// SettingsErrInvalidPasswordMinNumCount returns "system:setting.invalidPasswordMinNumCount" as *errors.Error +// +// +// This function is auto-generated. +// +func SettingsErrInvalidPasswordMinNumCount(mm ...*settingsActionProps) *errors.Error { + var p = &settingsActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("password constraint minimum number count should not be a negative number", nil), + + errors.Meta("type", "invalidPasswordMinNumCount"), + errors.Meta("resource", "system:setting"), + + errors.Meta(settingsPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "settings.errors.invalidPasswordMinNumCount"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// SettingsErrInvalidPasswordMinSpecialCharCount returns "system:setting.invalidPasswordMinSpecialCharCount" as *errors.Error +// +// +// This function is auto-generated. +// +func SettingsErrInvalidPasswordMinSpecialCharCount(mm ...*settingsActionProps) *errors.Error { + var p = &settingsActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("password constraint minimum special character count should not be a negative number", nil), + + errors.Meta("type", "invalidPasswordMinSpecialCharCount"), + errors.Meta("resource", "system:setting"), + + errors.Meta(settingsPropsMetaKey{}, p), + + // translation namespace & key + errors.Meta(locale.ErrorMetaNamespace{}, "system"), + errors.Meta(locale.ErrorMetaKey{}, "settings.errors.invalidPasswordMinSpecialCharCount"), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// ********************************************************************************************************************* +// ********************************************************************************************************************* + +// recordAction is a service helper function wraps function that can return error +// +// It will wrap unrecognized/internal errors with generic errors. +// +// This function is auto-generated. +// +func (svc settings) recordAction(ctx context.Context, props *settingsActionProps, actionFn func(...*settingsActionProps) *settingsAction, err error) error { + if svc.actionlog == nil || actionFn == nil { + // action log disabled or no action fn passed, return error as-is + return err + } else if err == nil { + // action completed w/o error, record it + svc.actionlog.Record(ctx, actionFn(props).ToAction()) + return nil + } + + a := actionFn(props).ToAction() + + // Extracting error information and recording it as action + a.Error = err.Error() + + switch c := err.(type) { + case *errors.Error: + m := c.Meta() + + a.Error = err.Error() + a.Severity = actionlog.Severity(m.AsInt("severity")) + a.Description = props.Format(m.AsString(settingsLogMetaKey{}), err) + + if p, has := m[settingsPropsMetaKey{}]; has { + a.Meta = p.(*settingsActionProps).Serialize() + } + + svc.actionlog.Record(ctx, a) + default: + svc.actionlog.Record(ctx, a) + } + + // Original error is passed on + return err +} diff --git a/system/service/setting_actions.yaml b/system/service/setting_actions.yaml new file mode 100644 index 000000000..991b76949 --- /dev/null +++ b/system/service/setting_actions.yaml @@ -0,0 +1,52 @@ +# List of loggable service actions + +resource: system:setting +service: settings + +# Default sensitivity for actions +defaultActionSeverity: notice + +# default severity for errors +defaultErrorSeverity: alert + +import: + - github.com/cortezaproject/corteza-server/system/types + +props: + - name: settings + type: "*types.SettingValue" + fields: [ name, value ] + +actions: + - action: lookup + log: "looked-up for a setting" + severity: info + +errors: + - error: notAllowedToRead + message: "not allowed to read this setting" + log: "failed to read {{settings.name}}; insufficient permissions" + + - error: notAllowedToManage + message: "not allowed to manage this setting" + log: "failed to manage {{settings.name}}; insufficient permissions" + + - error: invalidPasswordMinLength + message: "password constraint minimum length should be at least 8 characters" + severity: warning + + - error: invalidPasswordMinUpperCase + message: "password constraint minimum upper case count should not be a negative number" + severity: warning + + - error: invalidPasswordMinLowerCase + message: "password constraint minimum lower case count should not be a negative number" + severity: warning + + - error: invalidPasswordMinNumCount + message: "password constraint minimum number count should not be a negative number" + severity: warning + + - error: invalidPasswordMinSpecialCharCount + message: "password constraint minimum special character count should not be a negative number" + severity: warning diff --git a/system/service/settings.go b/system/service/settings.go index 37c6d336b..5f7522321 100644 --- a/system/service/settings.go +++ b/system/service/settings.go @@ -3,6 +3,8 @@ package service import ( "context" "fmt" + "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/spf13/cast" "strings" "sync" "time" @@ -17,6 +19,7 @@ import ( type ( settings struct { + actionlog actionlog.Recorder store store.SettingValues accessControl accessController logger *zap.Logger @@ -44,13 +47,9 @@ type ( } ) -var ( - ErrNoReadPermission = fmt.Errorf("not allowed to read settings") - ErrNoManagePermission = fmt.Errorf("not allowed to manage settings") -) - -func Settings(ctx context.Context, s store.SettingValues, log *zap.Logger, ac accessController, current interface{}) *settings { +func Settings(ctx context.Context, s store.SettingValues, log *zap.Logger, ac accessController, al actionlog.Recorder, current interface{}) *settings { svc := &settings{ + actionlog: al, store: s, accessControl: ac, logger: log.Named("settings"), @@ -118,7 +117,7 @@ func (svc *settings) log(ctx context.Context, fields ...zapcore.Field) *zap.Logg func (svc *settings) FindByPrefix(ctx context.Context, pp ...string) (types.SettingValueSet, error) { if !svc.accessControl.CanReadSettings(ctx) { - return nil, ErrNoReadPermission + return nil, SettingsErrNotAllowedToRead() } return svc.findByPrefix(ctx, pp...) @@ -138,7 +137,7 @@ func (svc *settings) findByPrefix(ctx context.Context, pp ...string) (types.Sett func (svc *settings) Get(ctx context.Context, name string, ownedBy uint64) (out *types.SettingValue, err error) { if !svc.accessControl.CanReadSettings(ctx) { - return nil, ErrNoReadPermission + return nil, SettingsErrNotAllowedToRead() } out, err = store.LookupSettingValueByNameOwnedBy(ctx, svc.store, name, ownedBy) @@ -177,7 +176,11 @@ func (svc *settings) updateCurrent(ctx context.Context, vv types.SettingValueSet func (svc *settings) Set(ctx context.Context, v *types.SettingValue) (err error) { if !svc.accessControl.CanManageSettings(ctx) { - return ErrNoManagePermission + return SettingsErrNotAllowedToManage() + } + + if _, err = check(v); err != nil { + return } var current *types.SettingValue @@ -205,7 +208,11 @@ func (svc *settings) Set(ctx context.Context, v *types.SettingValue) (err error) func (svc *settings) BulkSet(ctx context.Context, vv types.SettingValueSet) (err error) { if !svc.accessControl.CanManageSettings(ctx) { - return ErrNoManagePermission + return SettingsErrNotAllowedToManage() + } + + if _, err = check(vv...); err != nil { + return } // Load current settings and get changed values @@ -253,7 +260,7 @@ func (svc *settings) logChange(ctx context.Context, vv types.SettingValueSet) { func (svc *settings) Delete(ctx context.Context, name string, ownedBy uint64) error { if !svc.accessControl.CanManageSettings(ctx) { - return ErrNoManagePermission + return SettingsErrNotAllowedToManage() } current, err := store.LookupSettingValueByNameOwnedBy(ctx, svc.store, name, ownedBy) @@ -276,3 +283,51 @@ func (svc *settings) Delete(ctx context.Context, name string, ownedBy uint64) er return svc.updateCurrent(ctx, vv) } + +// Check validates settings values +func check(vv ...*types.SettingValue) (ok bool, err error) { + set := append(types.SettingValueSet{}, vv...) + err = set.Walk(func(val *types.SettingValue) error { + // Password constraints: The min password length should be 8 + if val.Name == "auth.internal.password-constraints.min-length" { + if cast.ToUint64(val.String()) < passwordMinLength { + return SettingsErrInvalidPasswordMinLength() + } + } + + // Password constraints: The min upper case count should not be a negative number + if val.Name == "auth.internal.password-constraints.min-upper-case" { + if cast.ToInt(val.String()) < 0 { + return SettingsErrInvalidPasswordMinUpperCase() + } + } + + // Password constraints: The min lower case count should not be a negative number + if val.Name == "auth.internal.password-constraints.min-lower-case" { + if cast.ToInt(val.String()) < 0 { + return SettingsErrInvalidPasswordMinLowerCase() + } + } + + // Password constraints: The min number of numeric characters should not be a negative number + if val.Name == "auth.internal.password-constraints.min-num-count" { + if cast.ToInt(val.String()) < 0 { + return SettingsErrInvalidPasswordMinNumCount() + } + } + + // Password constraints: The min number of special characters should not be a negative number + if val.Name == "auth.internal.password-constraints.min-special-count" { + if cast.ToInt(val.String()) < 0 { + return SettingsErrInvalidPasswordMinSpecialCharCount() + } + } + + return err + }) + if err != nil { + return false, err + } + + return true, nil +} diff --git a/system/types/app_settings.go b/system/types/app_settings.go index a4158b8af..584fc114b 100644 --- a/system/types/app_settings.go +++ b/system/types/app_settings.go @@ -379,19 +379,19 @@ type ( PasswordSecurity bool `kv:"-" json:"passwordSecurity"` // The min password length - MinLength uint `kv:"min-length"` + MinLength uint `kv:"min-length" json:"minLength"` // Minimum number of uppercase letters in password - MinUpperCase uint `kv:"min-upper-case"` + MinUpperCase uint `kv:"min-upper-case" json:"minUpperCase"` // Minimum number of lowercase letters in password - MinLowerCase uint `kv:"min-lower-case"` + MinLowerCase uint `kv:"min-lower-case" json:"minLowerCase"` // The min number of numeric characters - MinNumCount uint `kv:"min-num-count"` + MinNumCount uint `kv:"min-num-count" json:"minNumCount"` // The min number of special characters - MinSpecialCount uint `kv:"min-special-count"` + MinSpecialCount uint `kv:"min-special-count" json:"minSpecialCount"` } ) diff --git a/tests/system/settings_test.go b/tests/system/settings_test.go index 8b16ef56a..cd2d7262c 100644 --- a/tests/system/settings_test.go +++ b/tests/system/settings_test.go @@ -43,7 +43,7 @@ func TestSettingsList_noPermissions(t *testing.T) { Header("Accept", "application/json"). Expect(t). Status(http.StatusOK). - Assert(helpers.AssertError("not allowed to read settings")). + Assert(helpers.AssertError("settings.errors.notAllowedToRead")). End() } @@ -84,10 +84,67 @@ func TestSettingsUpdate_noPermissions(t *testing.T) { JSON(`{"values":[]}`). Expect(t). Status(http.StatusOK). - Assert(helpers.AssertError("not allowed to manage settings")). + Assert(helpers.AssertError("settings.errors.notAllowedToManage")). End() } +func TestSettingsUpdate_validation(t *testing.T) { + h := newHelper(t) + helpers.AllowMe(h, types.ComponentRbacResource(), "settings.manage") + helpers.AllowMe(h, types.ComponentRbacResource(), "settings.read") + + // Password constraints: The min password length should be 8 + h.apiInit(). + Patch("/settings/"). + Header("Accept", "application/json"). + JSON(`{"values":[{"name":"auth.internal.password-constraints.min-length","value":"7"}]}`). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("settings.errors.invalidPasswordMinLength")). + End() + + // Password constraints: The min upper case count should not be a negative number + h.apiInit(). + Patch("/settings/"). + Header("Accept", "application/json"). + JSON(`{"values":[{"name":"auth.internal.password-constraints.min-upper-case","value":"-1"}]}`). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("settings.errors.invalidPasswordMinUpperCase")). + End() + + // Password constraints: The min lower case count should not be a negative number + h.apiInit(). + Patch("/settings/"). + Header("Accept", "application/json"). + JSON(`{"values":[{"name":"auth.internal.password-constraints.min-lower-case","value":"-1"}]}`). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("settings.errors.invalidPasswordMinLowerCase")). + End() + + // Password constraints: The min number of numeric characters should not be a negative number + h.apiInit(). + Patch("/settings/"). + Header("Accept", "application/json"). + JSON(`{"values":[{"name":"auth.internal.password-constraints.min-num-count","value":"-1"}]}`). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("settings.errors.invalidPasswordMinNumCount")). + End() + + // Password constraints: The min number of special characters should not be a negative number + h.apiInit(). + Patch("/settings/"). + Header("Accept", "application/json"). + JSON(`{"values":[{"name":"auth.internal.password-constraints.min-special-count","value":"-1"}]}`). + Expect(t). + Status(http.StatusOK). + Assert(helpers.AssertError("settings.errors.invalidPasswordMinSpecialCharCount")). + End() + +} + func TestSettingsGet(t *testing.T) { h := newHelper(t) helpers.AllowMe(h, types.ComponentRbacResource(), "settings.read") @@ -125,7 +182,7 @@ func TestSettingsGet_noPermissions(t *testing.T) { Header("Accept", "application/json"). Expect(t). Status(http.StatusOK). - Assert(helpers.AssertError("not allowed to read settings")). + Assert(helpers.AssertError("settings.errors.notAllowedToRead")). End() } @@ -138,7 +195,7 @@ func TestSettingsSet_noPermissions(t *testing.T) { Header("Accept", "application/json"). Expect(t). Status(http.StatusOK). - Assert(helpers.AssertError("not allowed to read settings")). + Assert(helpers.AssertError("settings.errors.notAllowedToRead")). End() }