Add validation for password while updating system settings
Also, adds setting actions file and updated all settings errors to translated error
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
498
system/service/setting_actions.gen.go
generated
Normal file
498
system/service/setting_actions.gen.go
generated
Normal file
@@ -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
|
||||
}
|
||||
52
system/service/setting_actions.yaml
Normal file
52
system/service/setting_actions.yaml
Normal file
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user