Fix record service tests
This commit is contained in:
@@ -106,7 +106,7 @@ func ComposeModulesReload(ctx context.Context, s store.Storer, r modelReloader)
|
||||
|
||||
mod.Fields = append(mod.Fields, fields...)
|
||||
|
||||
model, err = moduleToModel(ctx, r, ns, mod)
|
||||
model, err = ModuleToModel(ctx, r, ns, mod)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -119,7 +119,7 @@ func ComposeModulesReload(ctx context.Context, s store.Storer, r modelReloader)
|
||||
}
|
||||
|
||||
func ComposeModuleCreate(ctx context.Context, c modelCreator, ns *types.Namespace, mod *types.Module) (err error) {
|
||||
model, err := moduleToModel(ctx, c, ns, mod)
|
||||
model, err := ModuleToModel(ctx, c, ns, mod)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -131,11 +131,11 @@ func ComposeModuleCreate(ctx context.Context, c modelCreator, ns *types.Namespac
|
||||
}
|
||||
|
||||
func ComposeModuleUpdate(ctx context.Context, u modelUpdater, ns *types.Namespace, old, new *types.Module) (err error) {
|
||||
oldModel, err := moduleToModel(ctx, u, ns, old)
|
||||
oldModel, err := ModuleToModel(ctx, u, ns, old)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newModel, err := moduleToModel(ctx, u, ns, new)
|
||||
newModel, err := ModuleToModel(ctx, u, ns, new)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -148,11 +148,11 @@ func ComposeModuleUpdate(ctx context.Context, u modelUpdater, ns *types.Namespac
|
||||
}
|
||||
|
||||
func ComposeModuleFieldsUpdate(ctx context.Context, u attributeUpdater, ns *types.Namespace, old, new *types.Module) (err error) {
|
||||
oldModel, err := moduleToModel(ctx, u, ns, old)
|
||||
oldModel, err := ModuleToModel(ctx, u, ns, old)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
newModel, err := moduleToModel(ctx, u, ns, new)
|
||||
newModel, err := ModuleToModel(ctx, u, ns, new)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -168,7 +168,7 @@ func ComposeModuleFieldsUpdate(ctx context.Context, u attributeUpdater, ns *type
|
||||
}
|
||||
|
||||
func ComposeModuleDelete(ctx context.Context, d modelDeleter, ns *types.Namespace, mod *types.Module) (err error) {
|
||||
model, err := moduleToModel(ctx, d, ns, mod)
|
||||
model, err := ModuleToModel(ctx, d, ns, mod)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -198,7 +198,7 @@ func ComposeModuleModelFormatter(f identFormatter, ns *types.Namespace, mod *typ
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// Utilities
|
||||
|
||||
func moduleToModel(ctx context.Context, f identFormatter, ns *types.Namespace, mod *types.Module) (*dal.Model, error) {
|
||||
func ModuleToModel(ctx context.Context, f identFormatter, ns *types.Namespace, mod *types.Module) (*dal.Model, error) {
|
||||
formatter, tplParts, err := ComposeModuleModelFormatter(f, ns, mod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -315,7 +315,7 @@ func moduleFieldToAttribute(getCodec func(f *types.ModuleField) dal.Codec, ns *t
|
||||
}
|
||||
|
||||
switch strings.ToLower(kind) {
|
||||
case "bool":
|
||||
case "bool", "boolean":
|
||||
at := &dal.TypeBoolean{}
|
||||
out = dal.FullAttribute(f.Name, at, getCodec(f))
|
||||
case "datetime":
|
||||
|
||||
@@ -2,6 +2,7 @@ package dalutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
@@ -32,6 +33,7 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
// ComposeRecordsList iterates over results and collects all available records
|
||||
func ComposeRecordsList(ctx context.Context, s searcher, mod *types.Module, filter types.RecordFilter) (set types.RecordSet, outFilter types.RecordFilter, err error) {
|
||||
iter, err := prepIterator(ctx, s, mod, filter)
|
||||
if err != nil {
|
||||
@@ -74,7 +76,7 @@ func ComposeRecordUpdate(ctx context.Context, u updater, mod *types.Module, reco
|
||||
}
|
||||
|
||||
func ComposeRecordSoftDelete(ctx context.Context, u updater, invoker uint64, mod *types.Module, records ...*types.Record) (err error) {
|
||||
n := time.Now()
|
||||
n := time.Now().Round(time.Second).UTC()
|
||||
for _, r := range records {
|
||||
r.DeletedAt = &n
|
||||
r.DeletedBy = invoker
|
||||
@@ -121,26 +123,104 @@ func prepIterator(ctx context.Context, dal searcher, mod *types.Module, filter t
|
||||
return
|
||||
}
|
||||
|
||||
func drainIterator(ctx context.Context, iter dal.Iterator, mod *types.Module, filter types.RecordFilter) (set types.RecordSet, outFilter types.RecordFilter, err error) {
|
||||
// drains iterator and collects all records
|
||||
//
|
||||
// Collection of records is done with respect to check function and limit constraint on record filter
|
||||
// For any other filter constraint we assume that underlying DAL took care of it
|
||||
func drainIterator(ctx context.Context, iter dal.Iterator, mod *types.Module, f types.RecordFilter) (set types.RecordSet, outFilter types.RecordFilter, err error) {
|
||||
// close iterator after we've drained it
|
||||
defer iter.Close()
|
||||
|
||||
// Get the requested number of recrds
|
||||
set = make(types.RecordSet, 0, filter.Limit)
|
||||
i := 0
|
||||
err = WalkIterator(ctx, iter, mod, func(r *types.Record) error {
|
||||
set = append(set, r)
|
||||
i++
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
const (
|
||||
// minimum amount of records we need to re-fetch
|
||||
minRefetch = 10
|
||||
|
||||
// refetch 20% more records that we missed
|
||||
refetchFactor = 1.2
|
||||
)
|
||||
|
||||
if f.Check == nil {
|
||||
panic("filter check function not set, this is probably a mistake")
|
||||
}
|
||||
|
||||
var (
|
||||
// counter for false checks
|
||||
checked uint
|
||||
fetched uint
|
||||
ok bool
|
||||
r *types.Record
|
||||
)
|
||||
|
||||
// Get the requested number of record
|
||||
if f.Limit > 0 {
|
||||
set = make(types.RecordSet, 0, f.Limit)
|
||||
} else {
|
||||
set = make(types.RecordSet, 0, 1000)
|
||||
}
|
||||
|
||||
for f.Limit == 0 || uint(len(set)) < f.Limit {
|
||||
// reset counters every drain
|
||||
checked = 0
|
||||
fetched = 0
|
||||
|
||||
// drain whatever we fetched
|
||||
for iter.Next(ctx) {
|
||||
fetched++
|
||||
if err = iter.Err(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r = prepareRecordTarget(mod)
|
||||
if err = iter.Scan(r); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// check fetched record
|
||||
if ok, err = f.Check(r); err != nil {
|
||||
return
|
||||
} else if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
checked++
|
||||
set = append(set, r)
|
||||
}
|
||||
|
||||
// if an error occurred inside Next(),
|
||||
// we need to stop draining
|
||||
if err = iter.Err(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if fetched == 0 || f.Limit == 0 || (0 < f.Limit && fetched < f.Limit) {
|
||||
// do not re-fetch if:
|
||||
// 1) nothing was fetch in the previous run
|
||||
// 2) there was no limit (everything was fetched)
|
||||
// 3) there are less fetched items then value of limit
|
||||
break
|
||||
}
|
||||
|
||||
// Fetch more records
|
||||
if checked > 0 {
|
||||
howMuchMore := checked
|
||||
if howMuchMore < minRefetch {
|
||||
howMuchMore = minRefetch
|
||||
}
|
||||
|
||||
howMuchMore = uint(math.Floor(float64(howMuchMore) * refetchFactor))
|
||||
|
||||
// request more items
|
||||
if err = iter.More(howMuchMore, r); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make out filter
|
||||
outFilter = filter
|
||||
pp := filter.Paging.Clone()
|
||||
outFilter = f
|
||||
pp := f.Paging.Clone()
|
||||
|
||||
if len(set) > 0 && filter.PrevPage != nil {
|
||||
if len(set) > 0 && f.PrevPage != nil {
|
||||
pp.PrevPage, err = iter.BackCursor(set[0])
|
||||
if err != nil {
|
||||
return
|
||||
@@ -155,6 +235,7 @@ func drainIterator(ctx context.Context, iter dal.Iterator, mod *types.Module, fi
|
||||
}
|
||||
|
||||
outFilter.Paging = *pp
|
||||
outFilter.Total = uint(len(set))
|
||||
|
||||
return
|
||||
}
|
||||
@@ -177,11 +258,3 @@ func recToGetters(rr ...*types.Record) (out []dal.ValueGetter) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func recToGetter(rr ...*types.Record) (out dal.ValueGetter) {
|
||||
if len(rr) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
return recToGetters(rr...)[0]
|
||||
}
|
||||
|
||||
13
compose/service/access_control.gen.go
generated
13
compose/service/access_control.gen.go
generated
@@ -23,6 +23,7 @@ type (
|
||||
}
|
||||
|
||||
rbacService interface {
|
||||
Can(rbac.Session, string, rbac.Resource) bool
|
||||
Evaluate(rbac.Session, string, rbac.Resource) rbac.Evaluated
|
||||
Grant(context.Context, ...*rbac.Rule) error
|
||||
FindRulesByRoleID(roleID uint64) (rr rbac.RuleSet)
|
||||
@@ -45,14 +46,14 @@ func AccessControl(rms roleMemberSearcher) *accessControl {
|
||||
}
|
||||
}
|
||||
|
||||
func (svc accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
|
||||
return svc.rbac.Evaluate(rbac.ContextToSession(ctx), op, res).Can
|
||||
func (svc *accessControl) can(ctx context.Context, op string, res rbac.Resource) bool {
|
||||
return svc.rbac.Can(rbac.ContextToSession(ctx), op, res)
|
||||
}
|
||||
|
||||
// Effective returns a list of effective permissions for all given resource
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (svc accessControl) Effective(ctx context.Context, rr ...rbac.Resource) (ee rbac.EffectiveSet) {
|
||||
func (svc *accessControl) Effective(ctx context.Context, rr ...rbac.Resource) (ee rbac.EffectiveSet) {
|
||||
for _, res := range rr {
|
||||
r := res.RbacResource()
|
||||
for op := range rbacResourceOperations(r) {
|
||||
@@ -66,7 +67,7 @@ func (svc accessControl) Effective(ctx context.Context, rr ...rbac.Resource) (ee
|
||||
// Evaluate returns a list of permissions evaluated for the given user/roles combo
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (svc accessControl) Evaluate(ctx context.Context, userID uint64, roles []uint64, rr ...string) (ee rbac.EvaluatedSet, err error) {
|
||||
func (svc *accessControl) Evaluate(ctx context.Context, userID uint64, roles []uint64, rr ...string) (ee rbac.EvaluatedSet, err error) {
|
||||
// Reusing the grant permission since this is who the feature is for
|
||||
if !svc.CanGrant(ctx) {
|
||||
// @todo should be altered to check grant permissions PER resource
|
||||
@@ -122,7 +123,7 @@ func (svc accessControl) Evaluate(ctx context.Context, userID uint64, roles []ui
|
||||
// Resources returns list of resources
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (svc accessControl) Resources() []rbac.Resource {
|
||||
func (svc *accessControl) Resources() []rbac.Resource {
|
||||
return []rbac.Resource{
|
||||
rbac.NewResource(types.ChartRbacResource(0, 0)),
|
||||
rbac.NewResource(types.ModuleRbacResource(0, 0)),
|
||||
@@ -137,7 +138,7 @@ func (svc accessControl) Resources() []rbac.Resource {
|
||||
// List returns list of operations on all resources
|
||||
//
|
||||
// This function is auto-generated
|
||||
func (svc accessControl) List() (out []map[string]string) {
|
||||
func (svc *accessControl) List() (out []map[string]string) {
|
||||
def := []map[string]string{
|
||||
{
|
||||
"type": types.ChartResourceType,
|
||||
|
||||
@@ -46,7 +46,7 @@ func TestCharts(t *testing.T) {
|
||||
|
||||
t.Run("crud", func(t *testing.T) {
|
||||
req := require.New(t)
|
||||
svc := chart{
|
||||
svc := &chart{
|
||||
store: s,
|
||||
ac: &accessControl{rbac: &rbac.ServiceAllowAll{}},
|
||||
}
|
||||
|
||||
38
compose/service/helpers_test.go
Normal file
38
compose/service/helpers_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
sysTypes "github.com/cortezaproject/corteza-server/system/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func resourceMaker(ctx context.Context, t *testing.T, s store.Storer, mods ...any) {
|
||||
req := require.New(t)
|
||||
|
||||
for _, m := range mods {
|
||||
switch c := m.(type) {
|
||||
case *sysTypes.User:
|
||||
t.Log("creating User")
|
||||
req.NoError(store.CreateUser(ctx, s, c))
|
||||
|
||||
case *sysTypes.Role:
|
||||
t.Log("creating Role")
|
||||
req.NoError(store.CreateRole(ctx, s, c))
|
||||
|
||||
case *types.Namespace:
|
||||
t.Log("creating Namespace")
|
||||
req.NoError(store.CreateComposeNamespace(ctx, s, c))
|
||||
|
||||
case *types.Module:
|
||||
t.Log("creating Module")
|
||||
req.NoError(store.CreateComposeModule(ctx, s, c))
|
||||
|
||||
case *types.ModuleField:
|
||||
t.Log("creating ModuleField")
|
||||
req.NoError(store.CreateComposeModuleField(ctx, s, c))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,13 +155,13 @@ type (
|
||||
ErrorIndex map[string]int
|
||||
)
|
||||
|
||||
func Record(dal dalDater) *record {
|
||||
func Record() *record {
|
||||
svc := &record{
|
||||
actionlog: DefaultActionlog,
|
||||
ac: DefaultAccessControl,
|
||||
eventbus: eventbus.Service(),
|
||||
store: DefaultStore,
|
||||
dal: dal,
|
||||
dal: dal.Service(),
|
||||
|
||||
formatter: values.Formatter(),
|
||||
sanitizer: values.Sanitizer(),
|
||||
@@ -190,7 +190,6 @@ func defaultValidator(svc RecordService) recordValuesValidator {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
//r, err := store.LookupComposeRecordByID(ctx, s, m, v.Ref)
|
||||
r, err := svc.FindByID(ctx, f.NamespaceID, f.ModuleID, v.Ref)
|
||||
return r != nil, err
|
||||
})
|
||||
@@ -930,7 +929,7 @@ func (svc record) procCreate(ctx context.Context, invokerID uint64, m *types.Mod
|
||||
// to make sure nobody slips in something we do not want
|
||||
new.ID = nextID()
|
||||
new.CreatedBy = invokerID
|
||||
new.CreatedAt = *now()
|
||||
new.CreatedAt = *nowUTC()
|
||||
new.UpdatedAt = nil
|
||||
new.UpdatedBy = 0
|
||||
new.DeletedAt = nil
|
||||
@@ -984,13 +983,13 @@ func (svc record) procUpdate(ctx context.Context, invokerID uint64, m *types.Mod
|
||||
// to make sure nobody slips in something we do not want
|
||||
upd.CreatedAt = old.CreatedAt
|
||||
upd.CreatedBy = old.CreatedBy
|
||||
upd.UpdatedAt = now()
|
||||
upd.UpdatedAt = nowUTC()
|
||||
upd.UpdatedBy = invokerID
|
||||
upd.DeletedAt = old.DeletedAt
|
||||
upd.DeletedBy = old.DeletedBy
|
||||
|
||||
if err := SetRecordOwner(ctx, svc.ac, svc.store, old, upd, invokerID); err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
upd.Values = old.Values.Merge(m.Fields, upd.Values, func(f *types.ModuleField) bool {
|
||||
@@ -998,10 +997,11 @@ func (svc record) procUpdate(ctx context.Context, invokerID uint64, m *types.Mod
|
||||
})
|
||||
|
||||
if rve = RecordValueUpdateOpCheck(ctx, svc.ac, m, upd.Values); !rve.IsValid() {
|
||||
return rve
|
||||
return
|
||||
}
|
||||
|
||||
return RecordPreparer(ctx, svc.store, svc.sanitizer, svc.validator, svc.formatter, m, upd)
|
||||
rve = RecordPreparer(ctx, svc.store, svc.sanitizer, svc.validator, svc.formatter, m, upd)
|
||||
return
|
||||
}
|
||||
|
||||
func (svc record) recordInfoUpdate(ctx context.Context, r *types.Record) {
|
||||
@@ -1443,7 +1443,7 @@ func (svc record) Iterator(ctx context.Context, f types.RecordFilter, fn eventbu
|
||||
recordableAction = RecordActionIteratorDelete
|
||||
|
||||
return store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) error {
|
||||
rec.DeletedAt = now()
|
||||
rec.DeletedAt = nowUTC()
|
||||
rec.DeletedBy = invokerID
|
||||
return dalutils.ComposeRecordSoftDelete(ctx, svc.dal, invokerID, m, rec)
|
||||
})
|
||||
|
||||
@@ -3,6 +3,10 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/compose/dalutils"
|
||||
"github.com/cortezaproject/corteza-server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza-server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza-server/store/adapters/rdbms"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/service/values"
|
||||
@@ -20,6 +24,147 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func makeTestRecordService(t *testing.T, mods ...any) *record {
|
||||
|
||||
var (
|
||||
err error
|
||||
req = require.New(t)
|
||||
log = zap.NewNop()
|
||||
)
|
||||
|
||||
for _, m := range mods {
|
||||
switch c := m.(type) {
|
||||
|
||||
case *zap.Logger:
|
||||
t.Log("using custom Logger to initialize Record service")
|
||||
log = c
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ctx = logger.ContextWithValue(context.Background(), log)
|
||||
svc = &record{
|
||||
sanitizer: values.Sanitizer(),
|
||||
formatter: values.Formatter(),
|
||||
eventbus: eventbus.New(),
|
||||
}
|
||||
)
|
||||
|
||||
svc.validator = defaultValidator(svc)
|
||||
|
||||
for _, m := range mods {
|
||||
switch c := m.(type) {
|
||||
case rbacService:
|
||||
t.Log("using custom RBAC to initialize Record service")
|
||||
svc.ac = &accessControl{rbac: c}
|
||||
case store.Storer:
|
||||
t.Log("using custom Store to initialize Record service")
|
||||
svc.store = c
|
||||
case dalService:
|
||||
t.Log("using custom DAL to initialize Record service")
|
||||
t.Log("make sure you manually reload models!")
|
||||
svc.dal = c
|
||||
}
|
||||
}
|
||||
|
||||
if svc.ac == nil {
|
||||
svc.ac = &accessControl{rbac: rbac.NewService(log, nil)}
|
||||
}
|
||||
|
||||
if svc.store == nil {
|
||||
svc.store, err = sqlite.ConnectInMemoryWithDebug(ctx)
|
||||
req.NoError(err)
|
||||
|
||||
req.NoError(store.Upgrade(ctx, log, svc.store))
|
||||
req.NoError(store.TruncateUsers(ctx, svc.store))
|
||||
req.NoError(store.TruncateRoles(ctx, svc.store))
|
||||
req.NoError(store.TruncateComposeNamespaces(ctx, svc.store))
|
||||
req.NoError(store.TruncateComposeModules(ctx, svc.store))
|
||||
req.NoError(store.TruncateComposeModuleFields(ctx, svc.store))
|
||||
req.NoError(store.TruncateRbacRules(ctx, svc.store))
|
||||
|
||||
}
|
||||
|
||||
resourceMaker(ctx, t, svc.store, mods...)
|
||||
|
||||
if svc.dal == nil {
|
||||
dalAux, err := dal.New(zap.NewNop(), true)
|
||||
req.NoError(err)
|
||||
|
||||
const (
|
||||
recordsTable = "compose_record"
|
||||
)
|
||||
|
||||
req.NoError(
|
||||
dalAux.ReplaceConnection(
|
||||
ctx,
|
||||
dal.MakeConnection(1, svc.store.ToDalConn(),
|
||||
dal.ConnectionParams{},
|
||||
dal.ConnectionMeta{DefaultModelIdent: recordsTable, DefaultAttributeIdent: "values"},
|
||||
),
|
||||
true,
|
||||
),
|
||||
)
|
||||
|
||||
svc.dal = dalAux
|
||||
|
||||
// assuming store will alwats be RDBMS/SQLite and we can just run delete
|
||||
//
|
||||
// this could be done more "properly" by invoking DAL to truncate records
|
||||
// but at the momment we need to provide the right model/module.
|
||||
_, err = svc.store.(*rdbms.Store).DB.ExecContext(ctx, "DELETE FROM "+recordsTable)
|
||||
req.NoError(err)
|
||||
|
||||
t.Log("reloading DAL models")
|
||||
req.NoError(dalutils.ComposeModulesReload(ctx, svc.store, dalAux))
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
func modelFilter(svc *record, moduleID uint64) *dal.Model {
|
||||
type (
|
||||
modelFinder interface {
|
||||
FindModelByResourceID(connectionID uint64, resourceID uint64) *dal.Model
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
aux any = svc.dal
|
||||
)
|
||||
|
||||
return aux.(modelFinder).FindModelByResourceID(1, moduleID)
|
||||
}
|
||||
|
||||
func verifyRecErrSet(t *testing.T, err any) {
|
||||
if rves, is := err.(*types.RecordValueErrorSet); is {
|
||||
if rves.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("Record errors:")
|
||||
for _, e := range rves.Set {
|
||||
t.Logf(" => [%s] %s", e.Kind, e.Message)
|
||||
t.Logf(" %v", e.Meta)
|
||||
}
|
||||
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if err, is := err.(*errors.Error); is {
|
||||
t.Errorf("unexpected error: %s", err.Error())
|
||||
for k, v := range err.Meta() {
|
||||
t.Errorf(" %+v: %+v", k, v)
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if err, is := err.(error); is {
|
||||
t.Errorf("unexpected error: %s", err.Error())
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneralValueSetValidation(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
@@ -94,103 +239,17 @@ func TestDefaultValueSetting(t *testing.T) {
|
||||
chk(out, "multi", 1, "m2")
|
||||
}
|
||||
|
||||
func TestProcUpdateOwnerPreservation(t *testing.T) {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
a = assert.New(t)
|
||||
|
||||
svc = record{
|
||||
sanitizer: values.Sanitizer(),
|
||||
validator: values.Validator(),
|
||||
formatter: values.Formatter(),
|
||||
}
|
||||
|
||||
mod = &types.Module{
|
||||
Fields: types.ModuleFieldSet{},
|
||||
}
|
||||
|
||||
oldRec = &types.Record{
|
||||
OwnedBy: 1,
|
||||
Values: types.RecordValueSet{},
|
||||
}
|
||||
newRec = &types.Record{
|
||||
OwnedBy: 0,
|
||||
Values: types.RecordValueSet{},
|
||||
}
|
||||
)
|
||||
|
||||
svc.procUpdate(ctx, 10, mod, newRec, oldRec)
|
||||
a.Equal(newRec.OwnedBy, uint64(1))
|
||||
svc.procUpdate(ctx, 10, mod, newRec, oldRec)
|
||||
a.Equal(newRec.OwnedBy, uint64(1))
|
||||
}
|
||||
|
||||
func TestProcUpdateOwnerChanged(t *testing.T) {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
a = assert.New(t)
|
||||
|
||||
svc = record{
|
||||
sanitizer: values.Sanitizer(),
|
||||
validator: values.Validator(),
|
||||
formatter: values.Formatter(),
|
||||
}
|
||||
|
||||
mod = &types.Module{
|
||||
Fields: types.ModuleFieldSet{},
|
||||
}
|
||||
|
||||
oldRec = &types.Record{
|
||||
OwnedBy: 1,
|
||||
Values: types.RecordValueSet{},
|
||||
}
|
||||
newRec = &types.Record{
|
||||
OwnedBy: 9,
|
||||
Values: types.RecordValueSet{},
|
||||
}
|
||||
)
|
||||
|
||||
a.True(svc.procUpdate(ctx, 10, mod, newRec, oldRec).IsValid())
|
||||
a.Equal(newRec.OwnedBy, uint64(9))
|
||||
a.True(svc.procUpdate(ctx, 10, mod, newRec, oldRec).IsValid())
|
||||
a.Equal(newRec.OwnedBy, uint64(9))
|
||||
}
|
||||
|
||||
func TestRecord_boolFieldPermissionIssueKBR(t *testing.T) {
|
||||
var (
|
||||
err error
|
||||
|
||||
ctx = context.Background()
|
||||
req = require.New(t)
|
||||
|
||||
// uncomment to enable sql conn debugging
|
||||
//ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
|
||||
ctx = context.Background()
|
||||
s, err = sqlite.ConnectInMemoryWithDebug(ctx)
|
||||
)
|
||||
u = &sysTypes.User{ID: nextID()}
|
||||
|
||||
req.NoError(err)
|
||||
req.NoError(store.Upgrade(ctx, zap.NewNop(), s))
|
||||
req.NoError(store.TruncateComposeNamespaces(ctx, s))
|
||||
req.NoError(store.TruncateComposeModules(ctx, s))
|
||||
req.NoError(store.TruncateComposeModuleFields(ctx, s))
|
||||
req.NoError(store.TruncateComposeRecords(ctx, s, nil))
|
||||
req.NoError(store.TruncateRbacRules(ctx, s))
|
||||
ns = &types.Namespace{ID: nextID()}
|
||||
|
||||
var (
|
||||
rbacService = rbac.NewService(
|
||||
//zap.NewNop(),
|
||||
logger.MakeDebugLogger(),
|
||||
nil,
|
||||
)
|
||||
ac = &accessControl{rbac: rbacService}
|
||||
|
||||
svc = &record{
|
||||
sanitizer: values.Sanitizer(),
|
||||
formatter: values.Formatter(),
|
||||
ac: ac,
|
||||
store: s,
|
||||
}
|
||||
|
||||
userID = nextID()
|
||||
ns = &types.Namespace{ID: nextID()}
|
||||
mod = &types.Module{ID: nextID(), NamespaceID: ns.ID}
|
||||
stringField = &types.ModuleField{ID: nextID(), ModuleID: mod.ID, Name: "string", Kind: "String"}
|
||||
boolField = &types.ModuleField{ID: nextID(), ModuleID: mod.ID, Name: "bool", Kind: "Boolean"}
|
||||
@@ -200,6 +259,23 @@ func TestRecord_boolFieldPermissionIssueKBR(t *testing.T) {
|
||||
readerRole = &sysTypes.Role{Name: "reader", ID: nextID()}
|
||||
writerRole = &sysTypes.Role{Name: "writer", ID: nextID()}
|
||||
|
||||
//
|
||||
rbacService = rbac.NewService(
|
||||
zap.NewNop(),
|
||||
//logger.MakeDebugLogger(),
|
||||
nil,
|
||||
)
|
||||
|
||||
svc = makeTestRecordService(t,
|
||||
rbacService,
|
||||
logger.MakeDebugLogger(),
|
||||
u,
|
||||
ns,
|
||||
mod,
|
||||
stringField,
|
||||
boolField,
|
||||
)
|
||||
|
||||
recChecked, recUnchecked *types.Record
|
||||
|
||||
valChecked = types.RecordValueSet{
|
||||
@@ -214,10 +290,6 @@ func TestRecord_boolFieldPermissionIssueKBR(t *testing.T) {
|
||||
|
||||
svc.validator = defaultValidator(svc)
|
||||
|
||||
req.NoError(store.CreateComposeNamespace(ctx, s, ns))
|
||||
req.NoError(store.CreateComposeModule(ctx, s, mod))
|
||||
req.NoError(store.CreateComposeModuleField(ctx, s, stringField, boolField))
|
||||
|
||||
rbacService.UpdateRoles(
|
||||
rbac.CommonRole.Make(readerRole.ID, readerRole.Name),
|
||||
rbac.CommonRole.Make(writerRole.ID, writerRole.Name),
|
||||
@@ -246,10 +318,10 @@ func TestRecord_boolFieldPermissionIssueKBR(t *testing.T) {
|
||||
|
||||
{
|
||||
// security context w/ writer role
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(userID, writerRole.ID, authRoleID))
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(u.ID, writerRole.ID, authRoleID))
|
||||
|
||||
recChecked, err = svc.Create(ctx, &types.Record{ModuleID: mod.ID, NamespaceID: ns.ID, Values: valChecked})
|
||||
req.NoError(errors.Unwrap(err))
|
||||
verifyRecErrSet(t, err)
|
||||
|
||||
req.NotNil(recChecked.Values.Get("bool", 0), "should be checked")
|
||||
req.Equal("1", recChecked.Values.Get("bool", 0).Value)
|
||||
@@ -262,7 +334,7 @@ func TestRecord_boolFieldPermissionIssueKBR(t *testing.T) {
|
||||
// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
|
||||
|
||||
// security context w/ reader role
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(userID, readerRole.ID, authRoleID))
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(u.ID, readerRole.ID, authRoleID))
|
||||
|
||||
recChecked.Values = types.RecordValueSet{
|
||||
&types.RecordValue{Name: "string", Value: "abc"},
|
||||
@@ -285,7 +357,7 @@ func TestRecord_boolFieldPermissionIssueKBR(t *testing.T) {
|
||||
// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
|
||||
|
||||
// security context w/ writer role
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(userID, writerRole.ID, authRoleID))
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(u.ID, writerRole.ID, authRoleID))
|
||||
|
||||
recChecked.Values = types.RecordValueSet{
|
||||
&types.RecordValue{Name: "string", Value: "abc"},
|
||||
@@ -302,48 +374,46 @@ func TestRecord_boolFieldPermissionIssueKBR(t *testing.T) {
|
||||
|
||||
func TestRecord_defValueFieldPermissionIssue(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
ctx = context.Background()
|
||||
s, err = sqlite.ConnectInMemoryWithDebug(ctx)
|
||||
)
|
||||
err error
|
||||
req = require.New(t)
|
||||
ctx = context.Background()
|
||||
|
||||
t.Log("setting up the test environment")
|
||||
|
||||
req.NoError(err)
|
||||
req.NoError(store.Upgrade(ctx, zap.NewNop(), s))
|
||||
req.NoError(store.TruncateComposeNamespaces(ctx, s))
|
||||
req.NoError(store.TruncateComposeModules(ctx, s))
|
||||
req.NoError(store.TruncateComposeModuleFields(ctx, s))
|
||||
req.NoError(store.TruncateComposeRecords(ctx, s, nil))
|
||||
req.NoError(store.TruncateRbacRules(ctx, s))
|
||||
|
||||
var (
|
||||
rbacService = rbac.NewService(
|
||||
//zap.NewNop(),
|
||||
logger.MakeDebugLogger(),
|
||||
nil,
|
||||
)
|
||||
ac = &accessControl{rbac: rbacService}
|
||||
|
||||
svc = &record{
|
||||
sanitizer: values.Sanitizer(),
|
||||
formatter: values.Formatter(),
|
||||
ac: ac,
|
||||
store: s,
|
||||
}
|
||||
user = &sysTypes.User{ID: nextID()}
|
||||
|
||||
ns = &types.Namespace{ID: nextID()}
|
||||
mod = &types.Module{ID: nextID(), NamespaceID: ns.ID}
|
||||
writableField = &types.ModuleField{ID: nextID(), ModuleID: mod.ID, NamespaceID: ns.ID, Name: "writable", Kind: "String", DefaultValue: types.RecordValueSet{{Value: "def-w"}}}
|
||||
readableField = &types.ModuleField{ID: nextID(), ModuleID: mod.ID, NamespaceID: ns.ID, Name: "readable", Kind: "String", DefaultValue: types.RecordValueSet{{Value: "def-r"}}}
|
||||
|
||||
userID = nextID()
|
||||
svc = makeTestRecordService(t,
|
||||
rbacService,
|
||||
user,
|
||||
ns,
|
||||
mod,
|
||||
writableField,
|
||||
readableField,
|
||||
)
|
||||
|
||||
authRoleID = nextID()
|
||||
editorRole = &sysTypes.Role{Name: "editor", ID: nextID()}
|
||||
|
||||
recPartial *types.Record
|
||||
|
||||
valueExtractor = func(rec *types.Record, ff ...string) (out string) {
|
||||
if rec == nil {
|
||||
return "<NIL RECORD>"
|
||||
}
|
||||
|
||||
if rec.Values == nil {
|
||||
return "<NIL RECORD VALUES>"
|
||||
}
|
||||
|
||||
for _, f := range ff {
|
||||
out += "<"
|
||||
if v := rec.Values.Get(f, 0); v != nil {
|
||||
@@ -362,10 +432,6 @@ func TestRecord_defValueFieldPermissionIssue(t *testing.T) {
|
||||
|
||||
svc.validator = defaultValidator(svc)
|
||||
|
||||
req.NoError(store.CreateComposeNamespace(ctx, s, ns))
|
||||
req.NoError(store.CreateComposeModule(ctx, s, mod))
|
||||
req.NoError(store.CreateComposeModuleField(ctx, s, writableField, readableField))
|
||||
|
||||
t.Log("setting up security")
|
||||
|
||||
rbacService.UpdateRoles(
|
||||
@@ -389,11 +455,10 @@ func TestRecord_defValueFieldPermissionIssue(t *testing.T) {
|
||||
{
|
||||
t.Log("creating record with w/o editor role (expecting defaults")
|
||||
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(userID, authRoleID))
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(user.ID, authRoleID))
|
||||
|
||||
recPartial, err = svc.Create(ctx, &types.Record{ModuleID: mod.ID, NamespaceID: ns.ID, Values: types.RecordValueSet{}})
|
||||
|
||||
req.NoError(errors.Unwrap(err))
|
||||
verifyRecErrSet(t, err)
|
||||
req.Equal("<def-w><def-r>", valueExtractor(recPartial, "writable", "readable"))
|
||||
|
||||
t.Log("creating record with w/o editor role (must be able to crate & update record and modify both fields)")
|
||||
@@ -403,7 +468,7 @@ func TestRecord_defValueFieldPermissionIssue(t *testing.T) {
|
||||
&types.RecordValue{Name: "readable", Value: "r"},
|
||||
}})
|
||||
|
||||
req.NoError(errors.Unwrap(err))
|
||||
verifyRecErrSet(t, err)
|
||||
req.Equal("<w><r>", valueExtractor(recPartial, "writable", "readable"))
|
||||
|
||||
t.Log("updating record removing one of the values")
|
||||
@@ -411,18 +476,17 @@ func TestRecord_defValueFieldPermissionIssue(t *testing.T) {
|
||||
recPartial.Values = types.RecordValueSet{&types.RecordValue{Name: "writable", Value: "w2"}}
|
||||
|
||||
recPartial, err = svc.Update(ctx, recPartial)
|
||||
req.NoError(errors.Unwrap(err))
|
||||
verifyRecErrSet(t, err)
|
||||
req.Equal("<w2><NULL>", valueExtractor(recPartial, "writable", "readable"))
|
||||
}
|
||||
|
||||
{
|
||||
t.Log("creating record with editor role (expecting defaults")
|
||||
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(userID, authRoleID, editorRole.ID))
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(user.ID, authRoleID, editorRole.ID))
|
||||
|
||||
recPartial, err = svc.Create(ctx, &types.Record{ModuleID: mod.ID, NamespaceID: ns.ID, Values: types.RecordValueSet{}})
|
||||
|
||||
req.NoError(errors.Unwrap(err))
|
||||
verifyRecErrSet(t, err)
|
||||
req.Equal("<def-w><def-r>", valueExtractor(recPartial, "writable", "readable"))
|
||||
|
||||
t.Log("creating record with editor role (must be able to crate & update record and modify both fields)")
|
||||
@@ -433,7 +497,7 @@ func TestRecord_defValueFieldPermissionIssue(t *testing.T) {
|
||||
&types.RecordValue{Name: "readable", Value: "r"},
|
||||
}})
|
||||
|
||||
req.NoError(errors.Unwrap(err))
|
||||
verifyRecErrSet(t, err)
|
||||
req.Equal("<def-w><r>", valueExtractor(recPartial, "writable", "readable"))
|
||||
}
|
||||
}
|
||||
@@ -453,7 +517,6 @@ func TestRecord_refAccessControl(t *testing.T) {
|
||||
req.NoError(store.TruncateComposeNamespaces(ctx, s))
|
||||
req.NoError(store.TruncateComposeModules(ctx, s))
|
||||
req.NoError(store.TruncateComposeModuleFields(ctx, s))
|
||||
req.NoError(store.TruncateComposeRecords(ctx, s, nil))
|
||||
req.NoError(store.TruncateRbacRules(ctx, s))
|
||||
|
||||
var (
|
||||
@@ -462,22 +525,13 @@ func TestRecord_refAccessControl(t *testing.T) {
|
||||
logger.MakeDebugLogger(),
|
||||
nil,
|
||||
)
|
||||
ac = &accessControl{rbac: rbacService}
|
||||
|
||||
svc = &record{
|
||||
sanitizer: values.Sanitizer(),
|
||||
formatter: values.Formatter(),
|
||||
ac: ac,
|
||||
store: s,
|
||||
}
|
||||
|
||||
nextIDi uint64 = 1
|
||||
nextID = func() uint64 {
|
||||
nextIDi++
|
||||
return nextIDi
|
||||
}
|
||||
|
||||
userID = nextID()
|
||||
user = &sysTypes.User{ID: nextID()}
|
||||
ns = &types.Namespace{ID: nextID()}
|
||||
mod1 = &types.Module{ID: nextID(), NamespaceID: ns.ID, Name: "mod one"}
|
||||
mod2 = &types.Module{ID: nextID(), NamespaceID: ns.ID, Name: "mod two"}
|
||||
@@ -486,6 +540,16 @@ func TestRecord_refAccessControl(t *testing.T) {
|
||||
|
||||
testerRole = &sysTypes.Role{Name: "tester", ID: nextID()}
|
||||
|
||||
svc = makeTestRecordService(t,
|
||||
rbacService,
|
||||
user,
|
||||
ns,
|
||||
mod1,
|
||||
mod2,
|
||||
mod1strField,
|
||||
mod2refField,
|
||||
)
|
||||
|
||||
mod1rec1 = &types.Record{
|
||||
NamespaceID: ns.ID,
|
||||
ModuleID: mod1.ID,
|
||||
@@ -499,22 +563,13 @@ func TestRecord_refAccessControl(t *testing.T) {
|
||||
|
||||
svc.validator = defaultValidator(svc)
|
||||
|
||||
t.Log("create test namespace")
|
||||
req.NoError(store.CreateComposeNamespace(ctx, s, ns))
|
||||
|
||||
t.Log("create test modules")
|
||||
req.NoError(store.CreateComposeModule(ctx, s, mod1, mod2))
|
||||
|
||||
t.Log("create test module fields")
|
||||
req.NoError(store.CreateComposeModuleField(ctx, s, mod1strField, mod2refField))
|
||||
|
||||
t.Log("inform rbac service about new roles")
|
||||
rbacService.UpdateRoles(
|
||||
rbac.CommonRole.Make(testerRole.ID, testerRole.Name),
|
||||
)
|
||||
|
||||
t.Log("log-in with test user ")
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(userID, testerRole.ID))
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(user.ID, testerRole.ID))
|
||||
|
||||
_ = mod2rec1
|
||||
|
||||
@@ -524,7 +579,7 @@ func TestRecord_refAccessControl(t *testing.T) {
|
||||
req.EqualError(err, "not allowed to create records")
|
||||
|
||||
t.Logf("granting permissions to create records on this module")
|
||||
rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, mod1.RbacResource(), "record.create"))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, mod1.RbacResource(), "record.create")))
|
||||
|
||||
t.Log("retry creating record on 1st module; should fail because we do not have permissions to update field")
|
||||
_, err = svc.Create(ctx, mod1rec1)
|
||||
@@ -532,7 +587,7 @@ func TestRecord_refAccessControl(t *testing.T) {
|
||||
req.True(types.IsRecordValueErrorSet(err).HasKind("updateDenied"))
|
||||
|
||||
t.Logf("granting permissions to update records values on module field")
|
||||
rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, mod1strField.RbacResource(), "record.value.update"))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, mod1strField.RbacResource(), "record.value.update")))
|
||||
|
||||
t.Log("retry creating record on 1st module; should succeed")
|
||||
mod1rec1, err = svc.Create(ctx, mod1rec1)
|
||||
@@ -552,21 +607,21 @@ func TestRecord_refAccessControl(t *testing.T) {
|
||||
req.EqualError(err, "not allowed to create records")
|
||||
|
||||
t.Log("grant record.create on namespace level")
|
||||
rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, types.ModuleRbacResource(ns.ID, 0), "record.create"))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, types.ModuleRbacResource(ns.ID, 0), "record.create")))
|
||||
|
||||
t.Log("grant record.value.update on namespace level")
|
||||
rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, types.ModuleFieldRbacResource(ns.ID, 0, 0), "record.value.update"))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, types.ModuleFieldRbacResource(ns.ID, 0, 0), "record.value.update")))
|
||||
|
||||
t.Log("create record on 2nd module with ref to record on the 1st module; most fail, not allowed to read (referenced) mod1rec1")
|
||||
_, err = svc.Create(ctx, mod2rec1)
|
||||
req.EqualError(err, "invalid record value input")
|
||||
|
||||
t.Log("grant read on record")
|
||||
rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, mod1rec1.RbacResource(), "read"))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, mod1rec1.RbacResource(), "read")))
|
||||
|
||||
t.Log("create record on 2nd module with ref to record on the 1st module")
|
||||
mod2rec1, err = svc.Create(ctx, mod2rec1)
|
||||
req.NoError(errors.Unwrap(err))
|
||||
verifyRecErrSet(t, err)
|
||||
}
|
||||
{
|
||||
t.Log("update record on 2nd module with unchanged values; must fail, no update permissions")
|
||||
@@ -574,64 +629,44 @@ func TestRecord_refAccessControl(t *testing.T) {
|
||||
req.EqualError(err, "not allowed to update this record")
|
||||
|
||||
t.Log("grant update on namespace level")
|
||||
rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, types.RecordRbacResource(ns.ID, 0, 0), "update"))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, types.RecordRbacResource(ns.ID, 0, 0), "update")))
|
||||
|
||||
t.Log("update record on 2nd module with unchanged values")
|
||||
mod2rec1, err = svc.Update(ctx, mod2rec1)
|
||||
req.NoError(errors.Unwrap(err))
|
||||
verifyRecErrSet(t, err)
|
||||
|
||||
t.Log("update record on 2nd module with unchanged values; unset record value")
|
||||
mod2rec1.Values = nil
|
||||
mod2rec1, err = svc.Update(ctx, mod2rec1)
|
||||
req.NoError(errors.Unwrap(err))
|
||||
verifyRecErrSet(t, err)
|
||||
|
||||
t.Log("link 2nd record to 1st one again")
|
||||
mod2rec1.Values = mod2rec1.Values.Set(&types.RecordValue{Name: "ref", Value: fmt.Sprintf("%d", mod1rec1.ID)})
|
||||
mod2rec1, err = svc.Update(ctx, mod2rec1)
|
||||
req.NoError(errors.Unwrap(err))
|
||||
verifyRecErrSet(t, err)
|
||||
}
|
||||
{
|
||||
t.Log("revoke read on record")
|
||||
rbacService.Grant(ctx, rbac.DenyRule(testerRole.ID, mod1rec1.RbacResource(), "read"))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.DenyRule(testerRole.ID, mod1rec1.RbacResource(), "read")))
|
||||
|
||||
t.Log("link 2nd record to 1st one again but w/o permissions; must work, value did not change")
|
||||
mod2rec1.Values = mod2rec1.Values.Set(&types.RecordValue{Name: "ref", Value: fmt.Sprintf("%d", mod1rec1.ID)})
|
||||
mod2rec1, err = svc.Update(ctx, mod2rec1)
|
||||
req.NoError(errors.Unwrap(err))
|
||||
verifyRecErrSet(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecord_searchAccessControl(t *testing.T) {
|
||||
var (
|
||||
err error
|
||||
req = require.New(t)
|
||||
ctx = context.Background()
|
||||
|
||||
// uncomment to enable sql conn debugging
|
||||
//ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
|
||||
ctx = context.Background()
|
||||
s, err = sqlite.ConnectInMemoryWithDebug(ctx)
|
||||
)
|
||||
|
||||
req.NoError(err)
|
||||
req.NoError(store.Upgrade(ctx, zap.NewNop(), s))
|
||||
req.NoError(store.TruncateComposeNamespaces(ctx, s))
|
||||
req.NoError(store.TruncateComposeModules(ctx, s))
|
||||
req.NoError(store.TruncateComposeModuleFields(ctx, s))
|
||||
req.NoError(store.TruncateComposeRecords(ctx, s, nil))
|
||||
req.NoError(store.TruncateRbacRules(ctx, s))
|
||||
|
||||
var (
|
||||
rbacService = rbac.NewService(
|
||||
//zap.NewNop(),
|
||||
logger.MakeDebugLogger(),
|
||||
nil,
|
||||
)
|
||||
ac = &accessControl{rbac: rbacService}
|
||||
|
||||
svc = &record{
|
||||
ac: ac,
|
||||
store: s,
|
||||
sanitizer: values.Sanitizer(),
|
||||
}
|
||||
|
||||
nextIDi uint64 = 1
|
||||
nextID = func() uint64 {
|
||||
@@ -639,10 +674,18 @@ func TestRecord_searchAccessControl(t *testing.T) {
|
||||
return nextIDi
|
||||
}
|
||||
|
||||
userID = nextID()
|
||||
ns = &types.Namespace{ID: nextID()}
|
||||
mod = &types.Module{ID: nextID(), NamespaceID: ns.ID, Name: "mod one"}
|
||||
//strField = &types.ModuleField{ID: nextID(), NamespaceID: ns.ID, ModuleID: mod.ID, Name: "str", Kind: "String"}
|
||||
user = &sysTypes.User{ID: nextID()}
|
||||
ns = &types.Namespace{ID: nextID()}
|
||||
mod = &types.Module{ID: nextID(), NamespaceID: ns.ID, Name: "mod one"}
|
||||
strField = &types.ModuleField{ID: nextID(), NamespaceID: ns.ID, ModuleID: mod.ID, Name: "str", Kind: "String"}
|
||||
|
||||
svc = makeTestRecordService(t,
|
||||
rbacService,
|
||||
user,
|
||||
ns,
|
||||
mod,
|
||||
strField,
|
||||
)
|
||||
|
||||
testerRole = &sysTypes.Role{Name: "tester", ID: nextID()}
|
||||
|
||||
@@ -653,16 +696,12 @@ func TestRecord_searchAccessControl(t *testing.T) {
|
||||
ModuleID: mod.ID,
|
||||
NamespaceID: ns.ID,
|
||||
}
|
||||
|
||||
// search for module's model to be used as a filter
|
||||
model = modelFilter(svc, mod.ID)
|
||||
)
|
||||
|
||||
t.Log("create test namespace")
|
||||
req.NoError(store.CreateComposeNamespace(ctx, s, ns))
|
||||
|
||||
t.Log("create test modules")
|
||||
req.NoError(store.CreateComposeModule(ctx, s, mod))
|
||||
|
||||
//t.Log("create test module fields")
|
||||
//req.NoError(store.CreateComposeModuleField(ctx, s, strField))
|
||||
req.NotNil(model)
|
||||
|
||||
t.Log("create test records")
|
||||
for i := 0; i < cap(rr); i++ {
|
||||
@@ -672,7 +711,7 @@ func TestRecord_searchAccessControl(t *testing.T) {
|
||||
ModuleID: mod.ID,
|
||||
}
|
||||
|
||||
req.NoError(store.CreateComposeRecord(ctx, s, mod, rr[i]))
|
||||
req.NoError(svc.dal.Create(ctx, model.ToFilter(), nil, rr[i]))
|
||||
}
|
||||
|
||||
t.Log("inform rbac service about new roles")
|
||||
@@ -681,57 +720,38 @@ func TestRecord_searchAccessControl(t *testing.T) {
|
||||
)
|
||||
|
||||
t.Log("log-in with test user ")
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(userID, testerRole.ID))
|
||||
rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, mod.RbacResource(), "records.search"))
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(user.ID, testerRole.ID))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, mod.RbacResource(), "records.search")))
|
||||
|
||||
t.Log("search for the newly created records; should not find any (all denied)")
|
||||
f.IncTotal = true
|
||||
hits, f, err = svc.Find(ctx, f)
|
||||
req.NoError(err)
|
||||
req.Len(hits, 0)
|
||||
req.Equal(uint(0), f.Total)
|
||||
|
||||
t.Log("allow read access for two records")
|
||||
rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, rr[3].RbacResource(), "read"))
|
||||
rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, rr[6].RbacResource(), "read"))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, rr[3].RbacResource(), "read")))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(testerRole.ID, rr[6].RbacResource(), "read")))
|
||||
|
||||
t.Log("search for the newly created records; should find 2 we're allowed to read")
|
||||
f.IncTotal = true
|
||||
hits, f, err = svc.Find(ctx, f)
|
||||
req.NoError(err)
|
||||
req.Len(hits, 2)
|
||||
req.Equal(uint(2), f.Total)
|
||||
}
|
||||
|
||||
func TestRecord_contextualRolesAccessControl(t *testing.T) {
|
||||
var (
|
||||
err error
|
||||
req = require.New(t)
|
||||
ctx = context.Background()
|
||||
|
||||
// uncomment to enable sql conn debugging
|
||||
ctx = logger.ContextWithValue(context.Background(), logger.MakeDebugLogger())
|
||||
//ctx = context.Background()
|
||||
s, err = sqlite.ConnectInMemoryWithDebug(ctx)
|
||||
)
|
||||
//log = zap.NewNop()
|
||||
log = logger.MakeDebugLogger()
|
||||
|
||||
req.NoError(err)
|
||||
req.NoError(store.Upgrade(ctx, zap.NewNop(), s))
|
||||
req.NoError(store.TruncateComposeNamespaces(ctx, s))
|
||||
req.NoError(store.TruncateComposeModules(ctx, s))
|
||||
req.NoError(store.TruncateComposeModuleFields(ctx, s))
|
||||
req.NoError(store.TruncateComposeRecords(ctx, s, nil))
|
||||
req.NoError(store.TruncateRbacRules(ctx, s))
|
||||
|
||||
var (
|
||||
rbacService = rbac.NewService(
|
||||
//zap.NewNop(),
|
||||
logger.MakeDebugLogger(),
|
||||
nil,
|
||||
)
|
||||
ac = &accessControl{rbac: rbacService}
|
||||
|
||||
svc = &record{
|
||||
ac: ac,
|
||||
store: s,
|
||||
sanitizer: values.Sanitizer(),
|
||||
}
|
||||
rbacService = rbac.NewService(log, nil)
|
||||
|
||||
nextIDi uint64 = 1
|
||||
nextID = func() uint64 {
|
||||
@@ -739,12 +759,22 @@ func TestRecord_contextualRolesAccessControl(t *testing.T) {
|
||||
return nextIDi
|
||||
}
|
||||
|
||||
userID = nextID()
|
||||
user = &sysTypes.User{ID: nextID()}
|
||||
ns = &types.Namespace{ID: nextID()}
|
||||
mod = &types.Module{ID: nextID(), NamespaceID: ns.ID, Name: "mod one"}
|
||||
numField = &types.ModuleField{ID: nextID(), NamespaceID: ns.ID, ModuleID: mod.ID, Name: "num", Kind: "String"}
|
||||
boolField = &types.ModuleField{ID: nextID(), NamespaceID: ns.ID, ModuleID: mod.ID, Name: "yes", Kind: "String"}
|
||||
|
||||
svc = makeTestRecordService(t,
|
||||
rbacService,
|
||||
log,
|
||||
user,
|
||||
ns,
|
||||
mod,
|
||||
numField,
|
||||
boolField,
|
||||
)
|
||||
|
||||
baseRole = &sysTypes.Role{Name: "base", ID: nextID()}
|
||||
ownerRole = &sysTypes.Role{Name: "owner", ID: nextID()}
|
||||
truthyRole = &sysTypes.Role{Name: "whenBoolTrue", ID: nextID()}
|
||||
@@ -758,6 +788,9 @@ func TestRecord_contextualRolesAccessControl(t *testing.T) {
|
||||
NamespaceID: ns.ID,
|
||||
}
|
||||
|
||||
// search for module's model to be used as a filter
|
||||
model = modelFilter(svc, mod.ID)
|
||||
|
||||
// setting up rbac context role expression parser
|
||||
roleCheckFnMaker = func(expression string) func(scope map[string]interface{}) bool {
|
||||
p := expr.NewParser()
|
||||
@@ -778,17 +811,6 @@ func TestRecord_contextualRolesAccessControl(t *testing.T) {
|
||||
}
|
||||
)
|
||||
|
||||
t.Log("create test namespace")
|
||||
req.NoError(store.CreateComposeNamespace(ctx, s, ns))
|
||||
|
||||
mod.Fields = types.ModuleFieldSet{numField, boolField}
|
||||
numField.ModuleID = mod.ID
|
||||
boolField.ModuleID = mod.ID
|
||||
|
||||
t.Log("create test modules")
|
||||
req.NoError(store.CreateComposeModule(ctx, s, mod))
|
||||
req.NoError(store.CreateComposeModuleField(ctx, s, numField, boolField))
|
||||
|
||||
t.Log("create test records")
|
||||
for i := 0; i < cap(rr); i++ {
|
||||
rr[i] = &types.Record{
|
||||
@@ -799,7 +821,7 @@ func TestRecord_contextualRolesAccessControl(t *testing.T) {
|
||||
|
||||
if i%2 == 0 {
|
||||
// let's own half of the records
|
||||
rr[i].OwnedBy = userID
|
||||
rr[i].OwnedBy = user.ID
|
||||
}
|
||||
|
||||
if i%3 == 0 {
|
||||
@@ -812,7 +834,8 @@ func TestRecord_contextualRolesAccessControl(t *testing.T) {
|
||||
rr[i].Values = rr[i].Values.Set(&types.RecordValue{Name: "yes", Value: "1"})
|
||||
}
|
||||
|
||||
req.NoError(store.CreateComposeRecord(ctx, s, mod, rr[i]))
|
||||
req.NoError(svc.dal.Create(ctx, model.ToFilter(), nil, rr[i]))
|
||||
|
||||
}
|
||||
|
||||
// result
|
||||
@@ -832,28 +855,32 @@ func TestRecord_contextualRolesAccessControl(t *testing.T) {
|
||||
rbac.MakeContextRole(tttRole.ID, tttRole.Name, roleCheckFnMaker(`has(resource.values, "num") ? resource.values.num == 333 : false`), types.RecordResourceType),
|
||||
)
|
||||
|
||||
rbacService.Grant(ctx, rbac.AllowRule(baseRole.ID, types.ModuleRbacResource(0, 0), "records.search"))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(baseRole.ID, types.ModuleRbacResource(0, 0), "records.search")))
|
||||
|
||||
t.Log("log-in with test user")
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(userID, baseRole.ID))
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(user.ID, baseRole.ID))
|
||||
|
||||
t.Log("expecting not find any (all denied)")
|
||||
hits, _, err = svc.Find(ctx, f)
|
||||
req.NoError(err)
|
||||
req.Len(hits, 0)
|
||||
|
||||
t.Log("expecting to find 5 records (owned by us)")
|
||||
rbacService.Grant(ctx, rbac.AllowRule(ownerRole.ID, types.RecordRbacResource(0, 0, 0), "read"))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(ownerRole.ID, types.RecordRbacResource(0, 0, 0), "read")))
|
||||
hits, _, err = svc.Find(ctx, f)
|
||||
req.NoError(err)
|
||||
req.Len(hits, 5)
|
||||
|
||||
t.Log("expecting to find 2 records (owned by us and with true value for 'yes' field)")
|
||||
rbacService.Grant(ctx, rbac.AllowRule(truthyRole.ID, types.RecordRbacResource(0, 0, 0), "read"))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(truthyRole.ID, types.RecordRbacResource(0, 0, 0), "read")))
|
||||
hits, _, err = svc.Find(ctx, f)
|
||||
req.NoError(err)
|
||||
req.Len(hits, 8)
|
||||
|
||||
t.Log("expecting to find 2 records (owned by us and with true value for 'yes' field + 333 for num)")
|
||||
rbacService.Grant(ctx, rbac.AllowRule(tttRole.ID, types.RecordRbacResource(0, 0, 0), "read"))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(tttRole.ID, types.RecordRbacResource(0, 0, 0), "read")))
|
||||
hits, _, err = svc.Find(ctx, f)
|
||||
req.NoError(err)
|
||||
req.Len(hits, 9)
|
||||
}
|
||||
|
||||
@@ -871,13 +898,12 @@ func TestSetRecordOwner(t *testing.T) {
|
||||
req.NoError(store.Upgrade(ctx, zap.NewNop(), s))
|
||||
req.NoError(store.TruncateComposeNamespaces(ctx, s))
|
||||
req.NoError(store.TruncateComposeModules(ctx, s))
|
||||
req.NoError(store.TruncateComposeRecords(ctx, s, nil))
|
||||
req.NoError(store.TruncateRbacRules(ctx, s))
|
||||
|
||||
var (
|
||||
rbacService = rbac.NewService(
|
||||
//zap.NewNop(),
|
||||
logger.MakeDebugLogger(),
|
||||
zap.NewNop(),
|
||||
//logger.MakeDebugLogger(),
|
||||
nil,
|
||||
)
|
||||
ac = &accessControl{rbac: rbacService}
|
||||
@@ -888,8 +914,8 @@ func TestSetRecordOwner(t *testing.T) {
|
||||
|
||||
role = &sysTypes.Role{Name: "role-with-ownership-change-permission", ID: 3000}
|
||||
|
||||
mod = &types.Module{ID: 3}
|
||||
old, upd *types.Record
|
||||
rvse *types.RecordValueErrorSet
|
||||
)
|
||||
|
||||
rbacService.UpdateRoles(
|
||||
@@ -897,39 +923,42 @@ func TestSetRecordOwner(t *testing.T) {
|
||||
)
|
||||
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(role.ID, types.RecordRbacResource(0, 0, 0), "owner.manage")))
|
||||
req.NoError(rbacService.Grant(ctx, rbac.AllowRule(role.ID, types.ModuleRbacResource(0, 0), "owned-record.create")))
|
||||
req.NoError(store.CreateUser(ctx, s, invoker, originalOwner, alternativeOwner))
|
||||
|
||||
t.Run("invalid input", func(t *testing.T) {
|
||||
old, upd = nil, nil
|
||||
require.Nil(t, SetRecordOwner(ctx, ac, s, old, upd, invoker.ID))
|
||||
verifyRecErrSet(t, SetRecordOwner(ctx, ac, s, old, upd, invoker.ID))
|
||||
})
|
||||
|
||||
t.Run("new record owner is invoker", func(t *testing.T) {
|
||||
old, upd = nil, &types.Record{ID: 1, NamespaceID: 2, ModuleID: 3}
|
||||
old, upd = nil, &types.Record{ID: 1, NamespaceID: 2, ModuleID: mod.ID}
|
||||
upd.SetModule(mod)
|
||||
|
||||
// this must work, invoker can always set owner to self on a new record
|
||||
if rvse = SetRecordOwner(ctx, ac, s, old, upd, invoker.ID); !rvse.IsValid() {
|
||||
t.Fatalf("errors: %v", rvse.Set)
|
||||
}
|
||||
verifyRecErrSet(t, SetRecordOwner(ctx, ac, s, old, upd, invoker.ID))
|
||||
|
||||
req.Equal(upd.OwnedBy, invoker.ID)
|
||||
})
|
||||
|
||||
t.Run("deny setting to alternative owner on create", func(t *testing.T) {
|
||||
old, upd = nil, &types.Record{ID: 1, NamespaceID: 2, ModuleID: 3, OwnedBy: alternativeOwner.ID}
|
||||
upd.SetModule(mod)
|
||||
|
||||
// this must work, invoker can always set owner to self on a new record
|
||||
if rvse = SetRecordOwner(ctx, ac, s, old, upd, invoker.ID); rvse.IsValid() {
|
||||
if rvse := SetRecordOwner(ctx, ac, s, old, upd, invoker.ID); rvse.IsValid() {
|
||||
t.Fatalf("expecting error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("deny setting to alternative owner on update", func(t *testing.T) {
|
||||
old = &types.Record{ID: 1, NamespaceID: 2, ModuleID: 3, OwnedBy: originalOwner.ID}
|
||||
old.SetModule(mod)
|
||||
upd = &types.Record{ID: 1, NamespaceID: 2, ModuleID: 3, OwnedBy: alternativeOwner.ID}
|
||||
upd.SetModule(mod)
|
||||
|
||||
// this must work, invoker can always set owner to self on a new record
|
||||
if rvse = SetRecordOwner(ctx, ac, s, old, upd, invoker.ID); rvse.IsValid() {
|
||||
if rvse := SetRecordOwner(ctx, ac, s, old, upd, invoker.ID); rvse.IsValid() {
|
||||
t.Fatalf("expecting error")
|
||||
}
|
||||
})
|
||||
@@ -937,35 +966,31 @@ func TestSetRecordOwner(t *testing.T) {
|
||||
t.Run("allow setting to alternative owner on create", func(t *testing.T) {
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(invoker.ID, role.ID))
|
||||
old, upd = nil, &types.Record{ID: 1, NamespaceID: 2, ModuleID: 3, OwnedBy: alternativeOwner.ID}
|
||||
upd.SetModule(mod)
|
||||
|
||||
if rvse = SetRecordOwner(ctx, ac, s, old, upd, invoker.ID); !rvse.IsValid() {
|
||||
t.Fatalf("errors: %v", rvse.Set)
|
||||
}
|
||||
|
||||
verifyRecErrSet(t, SetRecordOwner(ctx, ac, s, old, upd, invoker.ID))
|
||||
req.Equal(upd.OwnedBy, alternativeOwner.ID)
|
||||
})
|
||||
|
||||
t.Run("allow setting to alternative owner on update", func(t *testing.T) {
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(invoker.ID, role.ID))
|
||||
old = &types.Record{ID: 1, NamespaceID: 2, ModuleID: 3, OwnedBy: originalOwner.ID}
|
||||
old.SetModule(mod)
|
||||
upd = &types.Record{ID: 1, NamespaceID: 2, ModuleID: 3, OwnedBy: alternativeOwner.ID}
|
||||
upd.SetModule(mod)
|
||||
|
||||
if rvse = SetRecordOwner(ctx, ac, s, old, upd, invoker.ID); !rvse.IsValid() {
|
||||
t.Fatalf("errors: %v", rvse.Set)
|
||||
}
|
||||
|
||||
verifyRecErrSet(t, SetRecordOwner(ctx, ac, s, old, upd, invoker.ID))
|
||||
req.Equal(upd.OwnedBy, alternativeOwner.ID)
|
||||
})
|
||||
|
||||
t.Run("allow setting to new owner to zerp", func(t *testing.T) {
|
||||
ctx = auth.SetIdentityToContext(ctx, auth.Authenticated(invoker.ID, role.ID))
|
||||
old = &types.Record{ID: 1, NamespaceID: 2, ModuleID: 3, OwnedBy: originalOwner.ID}
|
||||
old.SetModule(mod)
|
||||
upd = &types.Record{ID: 1, NamespaceID: 2, ModuleID: 3, OwnedBy: 0}
|
||||
upd.SetModule(mod)
|
||||
|
||||
if rvse = SetRecordOwner(ctx, ac, s, old, upd, invoker.ID); !rvse.IsValid() {
|
||||
t.Fatalf("errors: %v", rvse.Set)
|
||||
}
|
||||
|
||||
verifyRecErrSet(t, SetRecordOwner(ctx, ac, s, old, upd, invoker.ID))
|
||||
req.Equal(upd.OwnedBy, invoker.ID)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -78,6 +78,12 @@ var (
|
||||
return &c
|
||||
}
|
||||
|
||||
// wrapper around time.Now() that will aid service testing
|
||||
nowUTC = func() *time.Time {
|
||||
c := time.Now().Round(time.Second).UTC()
|
||||
return &c
|
||||
}
|
||||
|
||||
// wrapper around nextID that will aid service testing
|
||||
nextID = func() uint64 {
|
||||
return id.Next()
|
||||
@@ -172,7 +178,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, c Config)
|
||||
DefaultModule = Module(dal.Service())
|
||||
|
||||
DefaultImportSession = ImportSession()
|
||||
DefaultRecord = Record(dal.Service())
|
||||
DefaultRecord = Record()
|
||||
DefaultPage = Page()
|
||||
DefaultChart = Chart()
|
||||
DefaultNotification = Notification(c.UserFinder)
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/federation/service"
|
||||
"github.com/cortezaproject/corteza-server/federation/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza-server/pkg/dal"
|
||||
"github.com/cortezaproject/corteza-server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza-server/pkg/federation"
|
||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||
@@ -96,7 +95,7 @@ func (ctrl SyncData) ReadExposedAll(ctx context.Context, r *request.SyncDataRead
|
||||
|
||||
// todo - handle error properly
|
||||
// @todo !!!
|
||||
if list, _, err := (cs.Record(dal.Service())).Find(ctx, rf); err != nil || len(list) == 0 {
|
||||
if list, _, err := (cs.Record()).Find(ctx, rf); err != nil || len(list) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -224,7 +223,7 @@ func (ctrl SyncData) readExposed(ctx context.Context, r *request.SyncDataReadExp
|
||||
}
|
||||
|
||||
// @todo !!!
|
||||
list, f, err := (cs.Record(dal.Service())).Find(ctx, f)
|
||||
list, f, err := (cs.Record()).Find(ctx, f)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -93,6 +93,7 @@ type (
|
||||
// Iterator provides an interface for loading data from the underlying store
|
||||
Iterator interface {
|
||||
Next(ctx context.Context) bool
|
||||
More(uint, ValueGetter) error
|
||||
Err() error
|
||||
Scan(ValueSetter) error
|
||||
Close() error
|
||||
|
||||
@@ -340,6 +340,7 @@ func (svc *service) RemoveConnection(ctx context.Context, ID uint64) (err error)
|
||||
// // // // // // // // // // // // // // // // // // // // // // // // //
|
||||
// DML
|
||||
|
||||
// Create stores new data (create compose record)
|
||||
func (svc *service) Create(ctx context.Context, mf ModelFilter, capabilities capabilities.Set, rr ...ValueGetter) (err error) {
|
||||
if err = svc.canOpRecord(mf.ConnectionID, mf.ResourceID); err != nil {
|
||||
return wrapError("cannot create record", err)
|
||||
|
||||
@@ -2,10 +2,12 @@ package rbac
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type (
|
||||
ServiceAllowAll struct{ *service }
|
||||
// ServiceAllowAll constructs not-for-production RBAC service
|
||||
ServiceAllowAll struct{}
|
||||
)
|
||||
|
||||
func (ServiceAllowAll) Can(Session, string, Resource) bool {
|
||||
@@ -22,3 +24,11 @@ func (ServiceAllowAll) FindRulesByRoleID(uint64) (rr RuleSet) {
|
||||
func (ServiceAllowAll) Grant(context.Context, ...*Rule) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ServiceAllowAll) Evaluate(Session, string, Resource) Evaluated {
|
||||
return Evaluated{Access: Allow, Can: true}
|
||||
}
|
||||
|
||||
func (ServiceAllowAll) CloneRulesByRoleID(context.Context, uint64, ...uint64) error {
|
||||
return fmt.Errorf(" ServiceAllowAll does not support rule clonning")
|
||||
}
|
||||
|
||||
@@ -164,6 +164,19 @@ func (c *SimpleJsonDocColumn) Decode(raw any, r dal.ValueSetter) (err error) {
|
||||
}
|
||||
|
||||
for pos, v := range vv {
|
||||
// now, encode the value according to JSON format constraints
|
||||
switch attr.Type.(type) {
|
||||
case *dal.TypeBoolean:
|
||||
// for backward compatibility reasons
|
||||
// we need to cast true bool values to "1"
|
||||
// and use "" for other (false) values
|
||||
if cast.ToBool(v) {
|
||||
v = "1"
|
||||
} else {
|
||||
v = ""
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.SetValue(name, uint(pos), v); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user