3
0

Add support for handle, fix tests

This commit is contained in:
Denis Arh
2019-09-17 21:21:02 +02:00
parent 62bc97dbe9
commit 71a78d53a5
25 changed files with 452 additions and 160 deletions
+10
View File
@@ -7,6 +7,7 @@ import (
"github.com/spf13/cobra"
"github.com/titpetric/factory"
"github.com/cortezaproject/corteza-server/compose/commands"
migrate "github.com/cortezaproject/corteza-server/compose/db"
"github.com/cortezaproject/corteza-server/compose/rest"
"github.com/cortezaproject/corteza-server/compose/service"
@@ -63,6 +64,15 @@ func Configure() *cli.Config {
rest.MountRoutes,
},
AdtSubCommands: cli.CommandMakers{
func(ctx context.Context, c *cli.Config) *cobra.Command {
return commands.Importer(ctx, c)
},
func(ctx context.Context, c *cli.Config) *cobra.Command {
return commands.Exporter(ctx, c)
},
},
ProvisionMigrateDatabase: cli.Runners{
func(ctx context.Context, cmd *cobra.Command, c *cli.Config) error {
var db, err = factory.Database.Get(compose)
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
ALTER TABLE `compose_module` ADD `handle` VARCHAR(200) NOT NULL AFTER `id`;
ALTER TABLE `compose_page` ADD `handle` VARCHAR(200) NOT NULL AFTER `id`;
ALTER TABLE `compose_chart` ADD `handle` VARCHAR(200) NOT NULL AFTER `id`;
+20 -2
View File
@@ -15,6 +15,7 @@ type (
With(ctx context.Context, db *factory.DB) ChartRepository
FindByID(namespaceID, chartID uint64) (*types.Chart, error)
FindByHandle(namespaceID uint64, handle string) (c *types.Chart, err error)
Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error)
Create(mod *types.Chart) (*types.Chart, error)
Update(mod *types.Chart) (*types.Chart, error)
@@ -27,7 +28,8 @@ type (
)
const (
ErrChartNotFound = repositoryError("ChartNotFound")
ErrChartNotFound = repositoryError("ChartNotFound")
ErrChartHandleNotUnique = repositoryError("ChartHandleNotUnique")
)
func Chart(ctx context.Context, db *factory.DB) ChartRepository {
@@ -46,7 +48,7 @@ func (r chart) table() string {
func (r chart) columns() []string {
return []string{
"id", "rel_namespace", "name", "config",
"id", "rel_namespace", "handle", "name", "config",
"created_at", "updated_at", "deleted_at",
}
}
@@ -74,6 +76,22 @@ func (r chart) FindByID(namespaceID, chartID uint64) (*types.Chart, error) {
return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrChartNotFound)
}
func (r chart) FindByHandle(namespaceID uint64, handle string) (*types.Chart, error) {
var (
query = r.query().
Columns(r.columns()...).
Where("handle = ?", handle)
c = &types.Chart{}
)
if namespaceID > 0 {
query = query.Where("rel_namespace = ?", namespaceID)
}
return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrChartNotFound)
}
func (r chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) {
f = filter
+27 -38
View File
@@ -19,6 +19,8 @@ type (
With(ctx context.Context, db *factory.DB) ModuleRepository
FindByID(namespaceID, moduleID uint64) (*types.Module, error)
FindByName(namespaceID uint64, name string) (*types.Module, error)
FindByHandle(namespaceID uint64, handle string) (*types.Module, error)
Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error)
FindFields(moduleIDs ...uint64) (ff types.ModuleFieldSet, err error)
Create(mod *types.Module) (*types.Module, error)
@@ -32,8 +34,9 @@ type (
)
const (
ErrModuleNotFound = repositoryError("ModuleNotFound")
ErrModuleNameNotUnique = repositoryError("ModuleNameNotUnique")
ErrModuleNotFound = repositoryError("ModuleNotFound")
ErrModuleNameNotUnique = repositoryError("ModuleNameNotUnique")
ErrModuleHandleNotUnique = repositoryError("ModuleHandleNotUnique")
)
func Module(ctx context.Context, db *factory.DB) ModuleRepository {
@@ -56,7 +59,7 @@ func (r module) tableFields() string {
func (r module) columns() []string {
return []string{
"id", "rel_namespace", "name", "json",
"id", "rel_namespace", "handle", "name", "json",
"created_at", "updated_at", "deleted_at",
}
}
@@ -70,19 +73,32 @@ func (r module) query() squirrel.SelectBuilder {
}
func (r module) FindByID(namespaceID, moduleID uint64) (*types.Module, error) {
var (
query = r.query().
Columns(r.columns()...).
Where("id = ?", moduleID)
return r.findOneBy(namespaceID, "id", moduleID)
}
c = &types.Module{}
func (r module) FindByHandle(namespaceID uint64, handle string) (*types.Module, error) {
return r.findOneBy(namespaceID, "LOWER(handle)", strings.ToLower(strings.TrimSpace(handle)))
}
func (r module) FindByName(namespaceID uint64, name string) (*types.Module, error) {
return r.findOneBy(namespaceID, "LOWER(name)", strings.ToLower(strings.TrimSpace(name)))
}
func (r module) findOneBy(namespaceID uint64, field string, value interface{}) (*types.Module, error) {
var m = &types.Module{}
err := r.findOneInNamespaceBy(
namespaceID,
r.query().Columns(r.columns()...),
squirrel.Eq{field: value},
m,
)
if namespaceID > 0 {
query = query.Where("rel_namespace = ?", namespaceID)
if err == nil && m.ID == 0 {
return nil, ErrModuleNotFound
}
return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrModuleNotFound)
return m, nil
}
func (r module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) {
@@ -118,10 +134,6 @@ func (r module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.Mo
func (r module) Create(mod *types.Module) (*types.Module, error) {
var err error
if err = r.checkName(mod.NamespaceID, 0, mod.Name); err != nil {
return nil, err
}
mod.ID = factory.Sonyflake.NextID()
mod.CreatedAt = time.Now().Truncate(time.Second)
@@ -137,10 +149,6 @@ func (r module) Create(mod *types.Module) (*types.Module, error) {
}
func (r module) Update(mod *types.Module) (*types.Module, error) {
if err := r.checkName(mod.NamespaceID, 0, mod.Name); err != nil {
return nil, err
}
now := time.Now().Truncate(time.Second)
mod.UpdatedAt = &now
@@ -245,22 +253,3 @@ func (r module) FindFields(moduleIDs ...uint64) (ff types.ModuleFieldSet, err er
return ff, r.db().Select(&ff, sql, args...)
}
}
// Checks if there is another module in the namespace with the same name
func (r module) checkName(namespaceID, moduleID uint64, name string) error {
mm, f, err := r.Find(types.ModuleFilter{
NamespaceID: namespaceID,
Name: name,
PerPage: 1,
})
if err != nil {
return err
}
if f.Count == 0 || mm.FindByID(moduleID) == nil {
return nil
}
return ErrModuleNameNotUnique
}
+3 -1
View File
@@ -28,7 +28,9 @@ type (
)
const (
ErrNamespaceNotFound = repositoryError("NamespaceNotFound")
ErrNamespaceNotFound = repositoryError("NamespaceNotFound")
ErrNamespaceSlugNotUnique = repositoryError("NamespaceSlugNotUnique")
ErrNamespaceInvalidSlugFormat = repositoryError("NamespaceInvalidSlugFormat")
)
func Namespace(ctx context.Context, db *factory.DB) NamespaceRepository {
+21 -2
View File
@@ -15,6 +15,7 @@ type (
With(ctx context.Context, db *factory.DB) PageRepository
FindByID(namespaceID, pageID uint64) (*types.Page, error)
FindByHandle(namespaceID uint64, handle string) (*types.Page, error)
FindByModuleID(namespaceID, moduleID uint64) (*types.Page, error)
Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error)
@@ -31,7 +32,8 @@ type (
)
const (
ErrPageNotFound = repositoryError("PageNotFound")
ErrPageNotFound = repositoryError("PageNotFound")
ErrPageHandleNotUnique = repositoryError("PageHandleNotUnique")
)
func Page(ctx context.Context, db *factory.DB) PageRepository {
@@ -50,7 +52,8 @@ func (r page) table() string {
func (r page) columns() []string {
return []string{
"id", "rel_namespace", "self_id", "rel_module", "title",
"id", "rel_namespace", "self_id", "rel_module",
"handle", "title",
"blocks", "description", "visible", "weight",
"created_at", "updated_at", "deleted_at",
}
@@ -79,6 +82,22 @@ func (r page) FindByID(namespaceID, pageID uint64) (*types.Page, error) {
return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrPageNotFound)
}
func (r page) FindByHandle(namespaceID uint64, handle string) (*types.Page, error) {
var (
query = r.query().
Columns(r.columns()...).
Where("handle = ?", handle)
c = &types.Page{}
)
if namespaceID > 0 {
query = query.Where("rel_namespace = ?", namespaceID)
}
return c, isFound(r.fetchOne(c, query), c.ID > 0, ErrPageNotFound)
}
func (r page) FindByModuleID(namespaceID, moduleID uint64) (*types.Page, error) {
var (
query = r.query().
+15
View File
@@ -48,6 +48,21 @@ func (r *repository) db() *factory.DB {
return DB(r.ctx)
}
func (r repository) findOneInNamespaceBy(namespaceID uint64, q squirrel.SelectBuilder, eq squirrel.Eq, row interface{}) error {
q = q.Where(eq)
if namespaceID > 0 {
q = q.Where("rel_namespace = ?", namespaceID)
}
if err := r.fetchOne(row, q); err != nil {
row = nil
return err
}
return nil
}
// Fetches single row from table
func (r repository) fetchOne(one interface{}, q squirrel.SelectBuilder) (err error) {
return rh.FetchOne(r.db(), q, one)
+46 -6
View File
@@ -8,6 +8,7 @@ import (
"github.com/cortezaproject/corteza-server/compose/repository"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/handle"
)
type (
@@ -34,6 +35,7 @@ type (
With(ctx context.Context) ChartService
FindByID(namespaceID, chartID uint64) (*types.Chart, error)
FindByHandle(namespaceID uint64, handle string) (*types.Chart, error)
Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error)
Create(chart *types.Chart) (*types.Chart, error)
@@ -69,17 +71,29 @@ func (svc chart) With(ctx context.Context) ChartService {
// }
func (svc chart) FindByID(namespaceID, chartID uint64) (c *types.Chart, err error) {
if namespaceID == 0 {
return nil, ErrNamespaceRequired
}
if c, err = svc.chartRepo.FindByID(namespaceID, chartID); err != nil {
if _, err = svc.loadNamespace(namespaceID); err != nil {
return
} else {
return svc.checkPermissions(svc.chartRepo.FindByID(namespaceID, chartID))
}
}
func (svc chart) FindByHandle(namespaceID uint64, handle string) (c *types.Chart, err error) {
if _, err = svc.loadNamespace(namespaceID); err != nil {
return
} else {
return svc.checkPermissions(svc.chartRepo.FindByHandle(namespaceID, handle))
}
}
func (svc chart) checkPermissions(c *types.Chart, err error) (*types.Chart, error) {
if err != nil {
return nil, err
} else if !svc.ac.CanReadChart(svc.ctx, c) {
return nil, ErrNoReadPermissions.withStack()
}
return
return c, err
}
func (svc chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error) {
@@ -96,16 +110,28 @@ func (svc chart) Find(filter types.ChartFilter) (set types.ChartSet, f types.Cha
}
func (svc chart) Create(mod *types.Chart) (c *types.Chart, err error) {
if !handle.IsValid(mod.Handle) {
return nil, ErrInvalidHandle
}
if ns, err := svc.loadNamespace(mod.NamespaceID); err != nil {
return nil, err
} else if !svc.ac.CanCreateChart(svc.ctx, ns) {
return nil, ErrNoCreatePermissions.withStack()
}
if err = svc.UniqueCheck(mod); err != nil {
return
}
return svc.chartRepo.Create(mod)
}
func (svc chart) Update(mod *types.Chart) (c *types.Chart, err error) {
if !handle.IsValid(mod.Handle) {
return nil, ErrInvalidHandle
}
if mod.ID == 0 {
return nil, ErrInvalidID.withStack()
}
@@ -118,6 +144,10 @@ func (svc chart) Update(mod *types.Chart) (c *types.Chart, err error) {
return nil, ErrStaleData.withStack()
}
if err = svc.UniqueCheck(mod); err != nil {
return
}
if !svc.ac.CanUpdateChart(svc.ctx, c) {
return nil, ErrNoUpdatePermissions.withStack()
}
@@ -146,6 +176,16 @@ func (svc chart) DeleteByID(namespaceID, chartID uint64) error {
return svc.chartRepo.DeleteByID(namespaceID, chartID)
}
func (svc chart) UniqueCheck(c *types.Chart) (err error) {
if c.Handle != "" {
if e, _ := svc.chartRepo.FindByHandle(c.NamespaceID, c.Handle); e != nil && e.ID != c.ID {
return repository.ErrChartHandleNotUnique
}
}
return nil
}
func (svc chart) loadNamespace(namespaceID uint64) (ns *types.Namespace, err error) {
if namespaceID == 0 {
return nil, ErrNamespaceRequired.withStack()
+1
View File
@@ -10,6 +10,7 @@ type (
const (
ErrInvalidID serviceError = "InvalidID"
ErrInvalidHandle serviceError = "InvalidHandle"
ErrStaleData serviceError = "StaleData"
ErrNoPermissions serviceError = "NoPermissions"
ErrNoGrantPermissions serviceError = "NoGrantPermissions"
+61 -8
View File
@@ -8,6 +8,7 @@ import (
"github.com/cortezaproject/corteza-server/compose/repository"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/handle"
)
type (
@@ -35,6 +36,8 @@ type (
With(ctx context.Context) ModuleService
FindByID(namespaceID, moduleID uint64) (*types.Module, error)
FindByName(namespaceID uint64, name string) (*types.Module, error)
FindByHandle(namespaceID uint64, handle string) (*types.Module, error)
Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error)
Create(module *types.Module) (*types.Module, error)
@@ -71,19 +74,39 @@ func (svc module) With(ctx context.Context) ModuleService {
// }
func (svc module) FindByID(namespaceID, moduleID uint64) (m *types.Module, err error) {
if namespaceID == 0 {
return nil, ErrNamespaceRequired
}
if m, err = svc.moduleRepo.FindByID(namespaceID, moduleID); err != nil {
if _, err = svc.loadNamespace(namespaceID); err != nil {
return
} else {
return svc.loader(svc.moduleRepo.FindByID(namespaceID, moduleID))
}
}
func (svc module) FindByName(namespaceID uint64, name string) (m *types.Module, err error) {
if _, err = svc.loadNamespace(namespaceID); err != nil {
return
} else {
return svc.loader(svc.moduleRepo.FindByName(namespaceID, name))
}
}
func (svc module) FindByHandle(namespaceID uint64, handle string) (m *types.Module, err error) {
if _, err = svc.loadNamespace(namespaceID); err != nil {
return
} else {
return svc.loader(svc.moduleRepo.FindByHandle(namespaceID, handle))
}
}
func (svc module) loader(m *types.Module, err error) (*types.Module, error) {
if err != nil {
return nil, err
} else if !svc.ac.CanReadModule(svc.ctx, m) {
return nil, ErrNoReadPermissions.withStack()
}
var ff types.ModuleFieldSet
if ff, err = svc.moduleRepo.FindFields(m.ID); err != nil {
return
return nil, err
} else {
_ = ff.Walk(func(f *types.ModuleField) error {
m.Fields = append(m.Fields, f)
@@ -91,7 +114,7 @@ func (svc module) FindByID(namespaceID, moduleID uint64) (m *types.Module, err e
})
}
return
return m, err
}
func (svc module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error) {
@@ -119,11 +142,17 @@ func (svc module) Find(filter types.ModuleFilter) (set types.ModuleSet, f types.
}
func (svc module) Create(mod *types.Module) (*types.Module, error) {
if !handle.IsValid(mod.Handle) {
return nil, ErrInvalidHandle
}
if mod.NamespaceID == 0 {
return nil, ErrNamespaceRequired.withStack()
}
if err := svc.UniqueCheck(mod); err != nil {
return nil, err
}
if ns, err := svc.loadNamespace(mod.NamespaceID); err != nil {
return nil, err
} else if !svc.ac.CanCreateModule(svc.ctx, ns) {
@@ -138,10 +167,18 @@ func (svc module) Update(mod *types.Module) (m *types.Module, err error) {
return nil, ErrInvalidID.withStack()
}
if !handle.IsValid(mod.Handle) {
return nil, ErrInvalidHandle
}
if m, err = svc.moduleRepo.FindByID(mod.NamespaceID, mod.ID); err != nil {
return
}
if err = svc.UniqueCheck(mod); err != nil {
return
}
if isStale(mod.UpdatedAt, m.UpdatedAt, m.CreatedAt) {
return nil, ErrStaleData.withStack()
}
@@ -175,6 +212,22 @@ func (svc module) DeleteByID(namespaceID, moduleID uint64) error {
return svc.moduleRepo.DeleteByID(namespaceID, moduleID)
}
func (svc module) UniqueCheck(m *types.Module) (err error) {
if m.Handle != "" {
if e, _ := svc.moduleRepo.FindByHandle(m.NamespaceID, m.Handle); e != nil && e.ID > 0 && e.ID != m.ID {
return repository.ErrModuleHandleNotUnique
}
}
if m.Name != "" {
if e, _ := svc.moduleRepo.FindByName(m.NamespaceID, m.Name); e != nil && e.ID > 0 && e.ID != m.ID {
return repository.ErrModuleNameNotUnique
}
}
return nil
}
func (svc module) loadNamespace(namespaceID uint64) (ns *types.Namespace, err error) {
if namespaceID == 0 {
return nil, ErrNamespaceRequired.withStack()
+35 -14
View File
@@ -3,12 +3,14 @@ package service
import (
"context"
"github.com/davecgh/go-spew/spew"
"github.com/titpetric/factory"
"go.uber.org/zap"
"github.com/cortezaproject/corteza-server/compose/repository"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/internal/permissions"
"github.com/cortezaproject/corteza-server/pkg/handle"
)
type (
@@ -35,6 +37,7 @@ type (
With(ctx context.Context) NamespaceService
FindByID(namespaceID uint64) (*types.Namespace, error)
FindByHandle(handle string) (*types.Namespace, error)
Find(types.NamespaceFilter) (types.NamespaceSet, types.NamespaceFilter, error)
Create(namespace *types.Namespace) (*types.Namespace, error)
@@ -69,27 +72,25 @@ func (svc namespace) With(ctx context.Context) NamespaceService {
// }
func (svc namespace) FindByID(ID uint64) (ns *types.Namespace, err error) {
if ID == 0 {
return nil, ErrInvalidID.withStack()
}
return svc.checkPermissions(svc.namespaceRepo.FindByID(ID))
}
if ns, err = svc.namespaceRepo.FindByID(ID); err != nil {
return
} else if !svc.ac.CanReadNamespace(svc.ctx, ns) {
return nil, ErrNoReadPermissions.withStack()
}
return
func (svc namespace) FindByHandle(handle string) (ns *types.Namespace, err error) {
return svc.checkPermissions(svc.namespaceRepo.FindBySlug(handle))
}
func (svc namespace) FindBySlug(slug string) (ns *types.Namespace, err error) {
if ns, err = svc.namespaceRepo.FindBySlug(slug); err != nil {
return
} else if !svc.ac.CanReadNamespace(svc.ctx, ns) {
return svc.checkPermissions(svc.namespaceRepo.FindBySlug(slug))
}
func (svc namespace) checkPermissions(p *types.Namespace, err error) (*types.Namespace, error) {
if err != nil {
return nil, err
} else if !svc.ac.CanReadNamespace(svc.ctx, p) {
return nil, ErrNoReadPermissions.withStack()
}
return
return p, err
}
func (svc namespace) Find(filter types.NamespaceFilter) (set types.NamespaceSet, f types.NamespaceFilter, err error) {
@@ -107,6 +108,11 @@ func (svc namespace) Find(filter types.NamespaceFilter) (set types.NamespaceSet,
// Create adds namespace and presets access rules for role everyone
func (svc namespace) Create(mod *types.Namespace) (*types.Namespace, error) {
if !handle.IsValid(mod.Slug) {
spew.Dump(mod.Slug)
return nil, ErrInvalidHandle
}
if !svc.ac.CanCreateNamespace(svc.ctx) {
return nil, ErrNoCreatePermissions.withStack()
}
@@ -119,6 +125,11 @@ func (svc namespace) Update(mod *types.Namespace) (ns *types.Namespace, err erro
return nil, ErrInvalidID.withStack()
}
if !handle.IsValid(mod.Slug) {
spew.Dump(mod.Slug)
return nil, ErrInvalidHandle
}
ns, err = svc.FindByID(mod.ID)
if err != nil {
return nil, err
@@ -153,3 +164,13 @@ func (svc namespace) DeleteByID(namespaceID uint64) error {
return svc.namespaceRepo.DeleteByID(namespaceID)
}
func (svc namespace) UniqueCheck(ns *types.Namespace) (err error) {
if ns.Slug != "" {
if e, _ := svc.namespaceRepo.FindBySlug(ns.Slug); e != nil && e.ID != ns.ID {
return repository.ErrNamespaceSlugNotUnique
}
}
return nil
}
+40 -13
View File
@@ -3,12 +3,12 @@ package service
import (
"context"
"github.com/pkg/errors"
"github.com/titpetric/factory"
"go.uber.org/zap"
"github.com/cortezaproject/corteza-server/compose/repository"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/handle"
)
type (
@@ -36,6 +36,7 @@ type (
With(ctx context.Context) PageService
FindByID(namespaceID, pageID uint64) (*types.Page, error)
FindByHandle(namespaceID uint64, handle string) (*types.Page, error)
FindByModuleID(namespaceID, moduleID uint64) (*types.Page, error)
FindBySelfID(namespaceID, selfID uint64) (pages types.PageSet, f types.PageFilter, err error)
Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error)
@@ -77,18 +78,34 @@ func (svc page) With(ctx context.Context) PageService {
// }
func (svc page) FindByID(namespaceID, pageID uint64) (p *types.Page, err error) {
return svc.checkPermissions(svc.pageRepo.FindByID(namespaceID, pageID))
if _, err = svc.loadNamespace(namespaceID); err != nil {
return
} else {
return svc.checkPermissions(svc.pageRepo.FindByID(namespaceID, pageID))
}
}
func (svc page) FindByHandle(namespaceID uint64, handle string) (c *types.Page, err error) {
if _, err = svc.loadNamespace(namespaceID); err != nil {
return
} else {
return svc.checkPermissions(svc.pageRepo.FindByHandle(namespaceID, handle))
}
}
func (svc page) FindByModuleID(namespaceID, moduleID uint64) (p *types.Page, err error) {
return svc.checkPermissions(svc.pageRepo.FindByModuleID(namespaceID, moduleID))
if _, err = svc.loadNamespace(namespaceID); err != nil {
return
} else {
return svc.checkPermissions(svc.pageRepo.FindByModuleID(namespaceID, moduleID))
}
}
func (svc page) checkPermissions(p *types.Page, err error) (*types.Page, error) {
if err != nil {
return nil, err
} else if !svc.ac.CanReadPage(svc.ctx, p) {
return nil, errors.New("not allowed to access this page")
return nil, ErrNoReadPermissions.withStack()
}
return p, err
@@ -190,13 +207,17 @@ func (svc page) Reorder(namespaceID, selfID uint64, pageIDs []uint64) error {
func (svc page) Create(mod *types.Page) (p *types.Page, err error) {
mod.ID = 0
if !handle.IsValid(mod.Handle) {
return nil, ErrInvalidHandle
}
if ns, err := svc.loadNamespace(mod.NamespaceID); err != nil {
return nil, err
} else if !svc.ac.CanCreatePage(svc.ctx, ns) {
return nil, ErrNoCreatePermissions.withStack()
}
if err = svc.checkModulePage(mod); err != nil {
if err = svc.UniqueCheck(mod); err != nil {
return
}
@@ -209,6 +230,10 @@ func (svc page) Update(mod *types.Page) (p *types.Page, err error) {
return nil, ErrInvalidID.withStack()
}
if !handle.IsValid(mod.Handle) {
return nil, ErrInvalidHandle
}
if _, err = svc.loadNamespace(mod.NamespaceID); err != nil {
return
}
@@ -225,7 +250,7 @@ func (svc page) Update(mod *types.Page) (p *types.Page, err error) {
return nil, ErrNoUpdatePermissions.withStack()
}
if err = svc.checkModulePage(mod); err != nil {
if err = svc.UniqueCheck(mod); err != nil {
return
}
@@ -241,13 +266,15 @@ func (svc page) Update(mod *types.Page) (p *types.Page, err error) {
return
}
func (svc page) checkModulePage(mod *types.Page) error {
if mod.ModuleID > 0 {
if p, err := svc.pageRepo.FindByModuleID(mod.NamespaceID, mod.ModuleID); err != nil {
if err.Error() != repository.ErrPageNotFound.Error() {
return err
}
} else if p.ID > 0 && mod.ID != p.ID {
func (svc page) UniqueCheck(p *types.Page) (err error) {
if p.Handle != "" {
if e, _ := svc.pageRepo.FindByHandle(p.NamespaceID, p.Handle); e != nil && e.ID != p.ID {
return repository.ErrPageHandleNotUnique
}
}
if p.ModuleID > 0 {
if p, _ := svc.pageRepo.FindByModuleID(p.NamespaceID, p.ModuleID); p.ID > 0 && p.ID != p.ID {
return ErrModulePageExists
}
}
+2 -1
View File
@@ -3,8 +3,9 @@ package types
import (
"time"
"github.com/cortezaproject/corteza-server/internal/permissions"
"github.com/jmoiron/sqlx/types"
"github.com/cortezaproject/corteza-server/internal/permissions"
)
type (
+1
View File
@@ -10,6 +10,7 @@ type (
const (
ErrInvalidID serviceError = "InvalidID"
ErrInvalidHandle serviceError = "InvalidHandle"
ErrNoPermissions serviceError = "NoPermissions"
ErrNoGrantPermissions serviceError = "NoGrantPermissions"
ErrNoCreatePermissions serviceError = "NoCreatePermissions"
+17 -28
View File
@@ -7,6 +7,7 @@ import (
"github.com/titpetric/factory"
"go.uber.org/zap"
"github.com/cortezaproject/corteza-server/pkg/handle"
"github.com/cortezaproject/corteza-server/system/repository"
"github.com/cortezaproject/corteza-server/system/types"
)
@@ -127,6 +128,11 @@ func (svc role) FindByHandle(handle string) (*types.Role, error) {
}
func (svc role) Create(mod *types.Role) (t *types.Role, err error) {
if !handle.IsValid(mod.Handle) {
return nil, ErrInvalidHandle
}
if !svc.ac.CanCreateRole(svc.ctx) {
return nil, ErrNoCreatePermissions.withStack()
}
@@ -146,6 +152,10 @@ func (svc role) Update(mod *types.Role) (t *types.Role, err error) {
return nil, ErrInvalidID
}
if !handle.IsValid(mod.Handle) {
return nil, ErrInvalidHandle
}
if !svc.ac.CanUpdateRole(svc.ctx, mod) {
return nil, ErrNoUpdatePermissions.withStack()
}
@@ -174,36 +184,15 @@ func (svc role) Update(mod *types.Role) (t *types.Role, err error) {
}
func (svc role) UniqueCheck(r *types.Role) (err error) {
var (
e *types.Role
checks = []struct {
query string
find func(string) (*types.Role, error)
err error
}{
// Checking scenario:
// if email/username/handle is found on another user, error is thrown
{r.Name, svc.FindByName, ErrRoleNameNotUnique},
{r.Handle, svc.FindByHandle, ErrRoleHandleNotUnique},
if r.Handle != "" {
if ex, _ := svc.role.FindByHandle(r.Handle); ex.ID > 0 && ex.ID != r.ID {
return ErrRoleHandleNotUnique
}
)
}
for _, c := range checks {
if c.query == "" {
// Skip empty values
continue
}
e, err = c.find(c.query)
if err == repository.ErrRoleNotFound {
// User not found, proceed to next check
continue
}
if e.ID > 0 && e.ID != r.ID {
// User found, throw configured error
return c.err
if r.Name != "" {
if ex, _ := svc.role.FindByName(r.Name); ex.ID > 0 && ex.ID != r.ID {
return ErrRoleNameNotUnique
}
}
+11 -27
View File
@@ -196,37 +196,21 @@ func (svc user) Update(mod *types.User) (u *types.User, err error) {
}
func (svc user) UniqueCheck(u *types.User) (err error) {
var (
e *types.User
checks = []struct {
query string
find func(string) (*types.User, error)
err error
}{
// Checking scenario:
// if email/username/handle is found on another user, error is thrown
{u.Email, svc.FindByEmail, ErrUserEmailNotUnique},
{u.Username, svc.FindByUsername, ErrUserUsernameNotUnique},
{u.Handle, svc.FindByHandle, ErrUserHandleNotUnique},
if u.Email != "" {
if ex, _ := svc.user.FindByEmail(u.Email); ex.ID > 0 && ex.ID != u.ID {
return ErrUserEmailNotUnique
}
)
}
for _, c := range checks {
if c.query == "" {
// Skip empty values
continue
if u.Username != "" {
if ex, _ := svc.user.FindByUsername(u.Username); ex.ID > 0 && ex.ID != u.ID {
return ErrUserUsernameNotUnique
}
}
e, err = c.find(c.query)
if err == repository.ErrUserNotFound {
// User not found, proceed to next check
continue
}
if e.ID > 0 && e.ID != u.ID {
// User found, throw configured error
return c.err
if u.Handle != "" {
if ex, _ := svc.user.FindByHandle(u.Handle); ex.ID > 0 && ex.ID != u.ID {
return ErrUserHandleNotUnique
}
}
+3
View File
@@ -123,6 +123,9 @@ func Configure() *cli.Config {
func(ctx context.Context, c *cli.Config) *cobra.Command {
return commands.Auth(ctx, c)
},
func(ctx context.Context, c *cli.Config) *cobra.Command {
return commands.Importer(ctx, c)
},
func(ctx context.Context, c *cli.Config) *cobra.Command {
return commands.Users(ctx, c)
},
+18 -1
View File
@@ -9,6 +9,7 @@ import (
jsonpath "github.com/steinfletcher/apitest-jsonpath"
"github.com/cortezaproject/corteza-server/compose/repository"
"github.com/cortezaproject/corteza-server/compose/service"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/tests/helpers"
)
@@ -20,7 +21,7 @@ func (h helper) repoChart() repository.ChartRepository {
func (h helper) repoMakeChart(ns *types.Namespace, name string) *types.Chart {
m, err := h.
repoChart().
Create(&types.Chart{Name: name, NamespaceID: ns.ID})
Create(&types.Chart{Name: name, Handle: name, NamespaceID: ns.ID})
h.a.NoError(err)
return m
@@ -44,6 +45,22 @@ func TestChartRead(t *testing.T) {
End()
}
func TestChartReadByHandle(t *testing.T) {
h := newHelper(t)
h.allow(types.NamespacePermissionResource.AppendWildcard(), "read")
h.allow(types.ChartPermissionResource.AppendWildcard(), "read")
ns := h.repoMakeNamespace("some-namespace")
c := h.repoMakeChart(ns, "some-chart")
cbh, err := service.DefaultChart.With(h.secCtx()).FindByHandle(ns.ID, c.Handle)
h.a.NoError(err)
h.a.NotNil(cbh)
h.a.Equal(cbh.ID, c.ID)
h.a.Equal(cbh.Handle, c.Handle)
}
func TestChartList(t *testing.T) {
h := newHelper(t)
+17
View File
@@ -9,6 +9,7 @@ import (
jsonpath "github.com/steinfletcher/apitest-jsonpath"
"github.com/cortezaproject/corteza-server/compose/repository"
"github.com/cortezaproject/corteza-server/compose/service"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/tests/helpers"
)
@@ -44,6 +45,22 @@ func TestModuleRead(t *testing.T) {
End()
}
func TestModuleReadByHandle(t *testing.T) {
h := newHelper(t)
h.allow(types.NamespacePermissionResource.AppendWildcard(), "read")
h.allow(types.ModulePermissionResource.AppendWildcard(), "read")
ns := h.repoMakeNamespace("some-namespace")
c := h.repoMakeModule(ns, "some-module")
cbh, err := service.DefaultModule.With(h.secCtx()).FindByHandle(ns.ID, c.Handle)
h.a.NoError(err)
h.a.NotNil(cbh)
h.a.Equal(cbh.ID, c.ID)
h.a.Equal(cbh.Handle, c.Handle)
}
func TestModuleList(t *testing.T) {
h := newHelper(t)
+18 -1
View File
@@ -9,7 +9,9 @@ import (
jsonpath "github.com/steinfletcher/apitest-jsonpath"
"github.com/cortezaproject/corteza-server/compose/repository"
"github.com/cortezaproject/corteza-server/compose/service"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/internal/rand"
"github.com/cortezaproject/corteza-server/tests/helpers"
)
@@ -20,7 +22,7 @@ func (h helper) repoNamespace() repository.NamespaceRepository {
func (h helper) repoMakeNamespace(name string) *types.Namespace {
ns, err := h.
repoNamespace().
Create(&types.Namespace{Name: name})
Create(&types.Namespace{Name: name, Slug: name})
h.a.NoError(err)
return ns
@@ -41,6 +43,21 @@ func TestNamespaceRead(t *testing.T) {
End()
}
func TestNamespaceReadByHandle(t *testing.T) {
h := newHelper(t)
h.allow(types.NamespacePermissionResource.AppendWildcard(), "read")
h.allow(types.NamespacePermissionResource.AppendWildcard(), "read")
ns := h.repoMakeNamespace("some-namespace-" + string(rand.Bytes(20)))
nsbh, err := service.DefaultNamespace.With(h.secCtx()).FindByHandle(ns.Slug)
h.a.NoError(err)
h.a.NotNil(nsbh)
h.a.Equal(nsbh.ID, ns.ID)
h.a.Equal(nsbh.Slug, ns.Slug)
}
func TestNamespaceList(t *testing.T) {
h := newHelper(t)
+17
View File
@@ -9,6 +9,7 @@ import (
jsonpath "github.com/steinfletcher/apitest-jsonpath"
"github.com/cortezaproject/corteza-server/compose/repository"
"github.com/cortezaproject/corteza-server/compose/service"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/tests/helpers"
)
@@ -44,6 +45,22 @@ func TestPageRead(t *testing.T) {
End()
}
func TestPageReadByHandle(t *testing.T) {
h := newHelper(t)
h.allow(types.NamespacePermissionResource.AppendWildcard(), "read")
h.allow(types.PagePermissionResource.AppendWildcard(), "read")
ns := h.repoMakeNamespace("some-namespace")
c := h.repoMakePage(ns, "some-page")
cbh, err := service.DefaultPage.With(h.secCtx()).FindByHandle(ns.ID, c.Handle)
h.a.NoError(err)
h.a.NotNil(cbh)
h.a.Equal(cbh.ID, c.ID)
h.a.Equal(cbh.Handle, c.Handle)
}
func TestPageList(t *testing.T) {
h := newHelper(t)
+10
View File
@@ -43,6 +43,16 @@ var (
p = permissions.NewTestService()
)
// random string, 10 chars long by default
func rs(a ...int) string {
var l = 10
if len(a) > 0 {
l = a[0]
}
return string(rand.Bytes(l))
}
func db() *factory.DB {
return factory.Database.MustGet("system").With(context.Background())
}
+54 -15
View File
@@ -6,9 +6,9 @@ import (
"net/http"
"testing"
"github.com/davecgh/go-spew/spew"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
"github.com/cortezaproject/corteza-server/internal/rand"
"github.com/cortezaproject/corteza-server/system/repository"
"github.com/cortezaproject/corteza-server/system/types"
"github.com/cortezaproject/corteza-server/tests/helpers"
@@ -18,19 +18,32 @@ func (h helper) repoRole() repository.RoleRepository {
return repository.Role(context.Background(), db())
}
func (h helper) repoMakeRole(name string) *types.Role {
u, err := h.
func (h helper) repoMakeRole(ss ...string) *types.Role {
var r = &types.Role{}
if len(ss) > 1 {
r.Handle = ss[1]
} else {
r.Handle = rs()
}
if len(ss) > 0 {
r.Name = ss[0]
} else {
r.Name = rs()
}
r, err := h.
repoRole().
Create(&types.Role{Name: name})
Create(r)
h.a.NoError(err)
return u
return r
}
func TestRoleRead(t *testing.T) {
h := newHelper(t)
u := h.repoMakeRole(string(rand.Bytes(10)))
u := h.repoMakeRole()
h.apiInit().
Get(fmt.Sprintf("/roles/%d", u.ID)).
@@ -61,21 +74,47 @@ func TestRoleCreateForbidden(t *testing.T) {
h.apiInit().
Post("/roles/").
FormData("name", string(rand.Bytes(10))).
FormData("name", rs()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("system.service.NoCreatePermissions")).
End()
}
func TestRoleCreateNotUnique(t *testing.T) {
h := newHelper(t)
h.allow(types.SystemPermissionResource, "role.create")
role := h.repoMakeRole()
spew.Dump(role)
h.apiInit().
Post("/roles/").
FormData("name", rs()).
FormData("handle", role.Handle).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("system.service.RoleHandleNotUnique")).
End()
h.apiInit().
Post("/roles/").
FormData("name", role.Name).
FormData("handle", rs()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertError("system.service.RoleNameNotUnique")).
End()
}
func TestRoleCreate(t *testing.T) {
h := newHelper(t)
h.allow(types.SystemPermissionResource, "role.create")
h.apiInit().
Post("/roles/").
FormData("name", string(rand.Bytes(10))).
FormData("handle", string(rand.Bytes(10))).
FormData("name", rs()).
FormData("handle", rs()).
Expect(t).
Status(http.StatusOK).
Assert(helpers.AssertNoErrors).
@@ -84,7 +123,7 @@ func TestRoleCreate(t *testing.T) {
func TestRoleUpdateForbidden(t *testing.T) {
h := newHelper(t)
u := h.repoMakeRole(string(rand.Bytes(10)))
u := h.repoMakeRole()
h.apiInit().
Put(fmt.Sprintf("/roles/%d", u.ID)).
@@ -97,11 +136,11 @@ func TestRoleUpdateForbidden(t *testing.T) {
func TestRoleUpdate(t *testing.T) {
h := newHelper(t)
u := h.repoMakeRole(string(rand.Bytes(10)))
u := h.repoMakeRole()
h.allow(types.RolePermissionResource.AppendWildcard(), "update")
newName := "updated-" + string(rand.Bytes(10))
newHandle := "updated-" + string(rand.Bytes(10))
newName := "updated-" + rs()
newHandle := "updated-" + rs()
h.apiInit().
Put(fmt.Sprintf("/roles/%d", u.ID)).
@@ -121,7 +160,7 @@ func TestRoleUpdate(t *testing.T) {
func TestRoleDeleteForbidden(t *testing.T) {
h := newHelper(t)
u := h.repoMakeRole(string(rand.Bytes(10)))
u := h.repoMakeRole()
h.apiInit().
Delete(fmt.Sprintf("/roles/%d", u.ID)).
@@ -135,7 +174,7 @@ func TestRoleDelete(t *testing.T) {
h := newHelper(t)
h.allow(types.RolePermissionResource.AppendWildcard(), "delete")
u := h.repoMakeRole(string(rand.Bytes(10)))
u := h.repoMakeRole()
h.apiInit().
Delete(fmt.Sprintf("/roles/%d", u.ID)).
+1 -2
View File
@@ -7,7 +7,6 @@ import (
"testing"
jsonpath "github.com/steinfletcher/apitest-jsonpath"
"github.com/titpetric/factory"
"github.com/cortezaproject/corteza-server/system/repository"
"github.com/cortezaproject/corteza-server/system/types"
@@ -15,7 +14,7 @@ import (
)
func (h helper) randEmail() string {
return fmt.Sprintf("%d@test.tld", factory.Sonyflake.NextID())
return fmt.Sprintf("%s@test.tld", rs())
}
func (h helper) repoUser() repository.UserRepository {