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.
321 lines
6.8 KiB
Go
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
|
|
}
|