3
0
Files
corteza/server/pkg/provision/pages.go
Tomaž Jerman 41cbe2fb65 Update page->layout migration to include RBAC rules
Apply page RBAC rules to the _forked_ page layout resource.
This is under an assumption that the same rules would be applied
on both places.
2023-05-26 12:13:23 +02:00

321 lines
6.8 KiB
Go

package provision
import (
"context"
"strings"
"github.com/cortezaproject/corteza/server/compose/types"
"github.com/cortezaproject/corteza/server/pkg/filter"
"github.com/cortezaproject/corteza/server/pkg/rbac"
"github.com/cortezaproject/corteza/server/store"
"github.com/cortezaproject/corteza/server/system/service"
systemTypes "github.com/cortezaproject/corteza/server/system/types"
"github.com/spf13/cast"
"go.uber.org/zap"
)
func migratePages(ctx context.Context, log *zap.Logger, s store.Storer) (err error) {
layouts, _, err := store.SearchComposePageLayouts(ctx, s, types.PageLayoutFilter{})
if err != nil {
return
}
// Probably already ran, no need to continue
if len(layouts) > 0 {
return
}
var (
crs *filter.PagingCursor
pages types.PageSet
auxf types.PageFilter
translations systemTypes.ResourceTranslationSet
nsRules rbac.RuleSet
pgRules rbac.RuleSet
)
return store.Tx(ctx, s, func(ctx context.Context, s store.Storer) (err error) {
translations, err = getRelevantTranslations(ctx, s)
if err != nil {
return
}
nsRules, pgRules, err = getRelevantRbacRules(ctx, s)
if err != nil {
return
}
for {
pages, auxf, err = store.SearchComposePages(ctx, s, types.PageFilter{
Deleted: filter.StateInclusive,
Paging: filter.Paging{
PageCursor: crs,
},
})
if err != nil {
return
}
if len(pages) == 0 {
break
}
err = migratePageChunk(ctx, s, nsRules, pgRules, translations, pages)
if err != nil {
return
}
crs = auxf.Paging.PageCursor
if crs == nil {
break
}
}
return nil
})
}
func getRelevantTranslations(ctx context.Context, s store.Storer) (out systemTypes.ResourceTranslationSet, err error) {
var (
crs *filter.PagingCursor
ll systemTypes.ResourceTranslationSet
auxf systemTypes.ResourceTranslationFilter
)
for {
ll, auxf, err = store.SearchResourceTranslations(ctx, s, systemTypes.ResourceTranslationFilter{
Paging: filter.Paging{
PageCursor: crs,
},
})
if err != nil {
return
}
if len(ll) == 0 {
break
}
for _, l := range ll {
if !strings.HasPrefix(l.K, "pageBlock.") {
out = append(out, l)
}
}
crs = auxf.Paging.PageCursor
if crs == nil {
break
}
}
return
}
func getRelevantRbacRules(ctx context.Context, s store.Storer) (nsOut, pgOut rbac.RuleSet, err error) {
ll, _, err := store.SearchRbacRules(ctx, s, rbac.RuleFilter{})
if err != nil {
return
}
for _, r := range ll {
if strings.HasPrefix(r.Resource, types.NamespaceResourceType) {
switch r.Operation {
case "pages.search", "page.create":
nsOut = append(nsOut, r)
}
}
if strings.HasPrefix(r.Resource, types.PageResourceType) {
switch r.Operation {
case "read", "update", "delete":
pgOut = append(pgOut, r)
}
}
}
return
}
func migratePageChunk(ctx context.Context, s store.Storer, nsRules, pgRules rbac.RuleSet, translations systemTypes.ResourceTranslationSet, pages types.PageSet) (err error) {
n := now()
for _, p := range pages {
// Base layout
ly := &types.PageLayout{
ID: nextID(),
NamespaceID: p.NamespaceID,
PageID: p.ID,
Handle: "primary",
Primary: true,
Weight: 1,
Meta: types.PageLayoutMeta{
Title: p.Title,
Description: p.Description,
},
Config: types.PageLayoutConfig{
Buttons: extractPageButtons(p),
},
CreatedAt: *n,
UpdatedAt: n,
DeletedAt: p.DeletedAt,
}
// Translations
tt := translations.FilterResource(p.ResourceTranslation())
// Button translations
sr := strings.NewReplacer("recordToolbar", "config.buttons")
for _, t := range tt {
if !strings.HasPrefix(t.K, "recordToolbar.") {
continue
}
t.K = sr.Replace(t.K)
t.Resource = ly.ResourceTranslation()
}
// Title, description
ttAux := tt.FilterKey(types.LocaleKeyPageTitle.Path)
for _, t := range ttAux {
tt = append(tt, &systemTypes.ResourceTranslation{
ID: nextID(),
Resource: ly.ResourceTranslation(),
K: types.LocaleKeyPageLayoutMetaTitle.Path,
Lang: t.Lang,
Message: t.Message,
CreatedAt: *n,
})
}
ttAux = tt.FilterKey(types.LocaleKeyPageDescription.Path)
for _, t := range ttAux {
tt = append(tt, &systemTypes.ResourceTranslation{
ID: nextID(),
Resource: ly.ResourceTranslation(),
K: types.LocaleKeyPageLayoutMetaDescription.Path,
Lang: t.Lang,
Message: t.Message,
CreatedAt: *n,
})
}
// RBAC
rr := make(rbac.RuleSet, 0, len(pgRules)+len(nsRules))
// - from namespace
for _, r := range nsRules {
nsRef := strings.Split(r.Resource, "/")[1]
pgRes := types.PageRbacResource(cast.ToUint64(nsRef), 0)
switch r.Operation {
case "pages.search":
rr = append(rr, &rbac.Rule{
RoleID: r.RoleID,
Resource: pgRes,
Operation: "page-layouts.search",
Access: r.Access,
})
case "page.create":
rr = append(rr, &rbac.Rule{
RoleID: r.RoleID,
Resource: pgRes,
Operation: "page-layout.create",
Access: r.Access,
})
}
}
// - from page
for _, r := range pgRules {
pp := strings.Split(r.Resource, "/")
nsRef := pp[1]
pgRef := pp[2]
lyRes := types.PageLayoutRbacResource(cast.ToUint64(nsRef), cast.ToUint64(pgRef), 0)
switch r.Operation {
case "read":
rr = append(rr, &rbac.Rule{
RoleID: r.RoleID,
Resource: lyRes,
Operation: "read",
Access: r.Access,
})
case "update":
rr = append(rr, &rbac.Rule{
RoleID: r.RoleID,
Resource: lyRes,
Operation: "update",
Access: r.Access,
})
case "delete":
rr = append(rr, &rbac.Rule{
RoleID: r.RoleID,
Resource: lyRes,
Operation: "delete",
Access: r.Access,
})
}
}
// Blocks
for _, b := range p.Blocks {
b.XYWH = adjustBlockScale(b.XYWH, 12, 48)
ly.Blocks = append(ly.Blocks, types.PageLayoutBlock{
BlockID: b.BlockID,
XYWH: b.XYWH,
})
}
err = store.UpsertComposePage(ctx, s, p)
if err != nil {
return
}
err = store.UpsertComposePageLayout(ctx, s, ly)
if err != nil {
return
}
err = store.UpsertResourceTranslation(ctx, s, tt...)
if err != nil {
return
}
err = store.UpsertRbacRule(ctx, s, rr...)
if err != nil {
return
}
}
return
}
func extractPageButtons(p *types.Page) (out types.PageLayoutButtonConfig) {
ss := service.CurrentSettings.Compose.UI.RecordToolbar
if p.ModuleID == 0 {
return
}
out = types.PageLayoutButtonConfig{}
out.New.Enabled = !ss.HideNew
out.Edit.Enabled = !ss.HideEdit
out.Submit.Enabled = !ss.HideSubmit
out.Delete.Enabled = !ss.HideDelete
out.Clone.Enabled = !ss.HideClone
out.Back.Enabled = !ss.HideBack
return
}
func adjustBlockScale(b [4]int, prev, new int) [4]int {
for i, v := range b {
b[i] = v * new / prev
}
return b
}