diff --git a/automation/service/locale.gen.go b/automation/service/locale.gen.go deleted file mode 100644 index 26c97ce89..000000000 --- a/automation/service/locale.gen.go +++ /dev/null @@ -1,154 +0,0 @@ -package service - -// This file is auto-generated. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// - -// Definitions file that controls how this file is generated: -// - automation.workflow.yaml - -import ( - "context" - "github.com/cortezaproject/corteza-server/automation/types" - - "github.com/cortezaproject/corteza-server/pkg/actionlog" - intAuth "github.com/cortezaproject/corteza-server/pkg/auth" - "github.com/cortezaproject/corteza-server/pkg/errors" - "github.com/cortezaproject/corteza-server/pkg/locale" - "github.com/cortezaproject/corteza-server/store" - systemTypes "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - resourceTranslationsManager struct { - actionlog actionlog.Recorder - locale locale.Resource - store store.Storer - ac localeAccessController - } - - localeAccessController interface { - CanManageResourceTranslations(context.Context) bool - } - - ResourceTranslationsManagerService interface { - Workflow(ctx context.Context, ID uint64) (locale.ResourceTranslationSet, error) - - Upsert(context.Context, locale.ResourceTranslationSet) error - Locale() locale.Resource - } -) - -var ErrNotAllowedToManageResourceTranslations = errors.Unauthorized("not allowed to manage resource translations") - -func ResourceTranslationsManager(ls locale.Resource) *resourceTranslationsManager { - return &resourceTranslationsManager{ - actionlog: DefaultActionlog, - store: DefaultStore, - ac: DefaultAccessControl, - locale: ls, - } -} - -func (svc resourceTranslationsManager) Upsert(ctx context.Context, rr locale.ResourceTranslationSet) (err error) { - // User is allowed to manage resource translations when: - // - managed resource translation strings are all for default language - // or - // - user is allowed to manage resource translations - if rr.ContainsForeign(svc.Locale().Default().Tag) { - if !svc.ac.CanManageResourceTranslations(ctx) { - return ErrNotAllowedToManageResourceTranslations - } - } - - // @todo validation - - me := intAuth.GetIdentityFromContext(ctx) - - // - group by resource - localeByRes := make(map[string]locale.ResourceTranslationSet) - for _, r := range rr { - localeByRes[r.Resource] = append(localeByRes[r.Resource], r) - } - - // - for each resource, fetch the current state - sysLocale := make(systemTypes.ResourceTranslationSet, 0, len(rr)) - for res, rr := range localeByRes { - current, _, err := store.SearchResourceTranslations(ctx, svc.store, systemTypes.ResourceTranslationFilter{ - Resource: res, - }) - if err != nil { - return err - } - - // get deltas and prepare upsert accordingly - aux := current.New(rr) - aux.Walk(func(cc *systemTypes.ResourceTranslation) error { - cc.ID = nextID() - cc.CreatedAt = *now() - cc.CreatedBy = me.Identity() - - return nil - }) - sysLocale = append(sysLocale, aux...) - - aux = current.Old(rr) - aux.Walk(func(cc *systemTypes.ResourceTranslation) error { - cc.UpdatedAt = now() - cc.UpdatedBy = me.Identity() - return nil - }) - sysLocale = append(sysLocale, aux...) - } - - err = store.UpsertResourceTranslation(ctx, svc.store, sysLocale...) - if err != nil { - return err - } - - // Reload ALL resource translations - // @todo we could probably do this more selectively and refresh only updated resources? - _ = locale.Global().ReloadResourceTranslations(ctx) - - return nil -} - -func (svc resourceTranslationsManager) Locale() locale.Resource { - return svc.locale -} - -func (svc resourceTranslationsManager) Workflow(ctx context.Context, ID uint64) (locale.ResourceTranslationSet, error) { - var ( - err error - out locale.ResourceTranslationSet - res *types.Workflow - k types.LocaleKey - ) - - res, err = svc.loadWorkflow(ctx, svc.store, ID) - if err != nil { - return nil, err - } - - for _, tag := range svc.locale.Tags() { - k = types.LocaleKeyWorkflowName - out = append(out, &locale.ResourceTranslation{ - Resource: res.ResourceTranslation(), - Lang: tag.String(), - Key: k.Path, - Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), k.Path), - }) - - k = types.LocaleKeyWorkflowDescription - out = append(out, &locale.ResourceTranslation{ - Resource: res.ResourceTranslation(), - Lang: tag.String(), - Key: k.Path, - Msg: svc.locale.TResourceFor(tag, res.ResourceTranslation(), k.Path), - }) - - } - return out, nil -} diff --git a/automation/service/locale.go b/automation/service/locale.go deleted file mode 100644 index de16afb47..000000000 --- a/automation/service/locale.go +++ /dev/null @@ -1,12 +0,0 @@ -package service - -import ( - "context" - - "github.com/cortezaproject/corteza-server/automation/types" - "github.com/cortezaproject/corteza-server/store" -) - -func (svc resourceTranslationsManager) loadWorkflow(ctx context.Context, s store.Storer, ID uint64) (*types.Workflow, error) { - return loadWorkflow(ctx, s, ID) -} diff --git a/compose/service/locale.gen.go b/compose/service/locale.gen.go index 7e70de770..d2428001e 100644 --- a/compose/service/locale.gen.go +++ b/compose/service/locale.gen.go @@ -72,6 +72,10 @@ func (svc resourceTranslationsManager) Upsert(ctx context.Context, rr locale.Res } } + for _, r := range rr { + r.Msg = locale.SanitizeMessage(r.Msg) + } + // @todo validation me := intAuth.GetIdentityFromContext(ctx) diff --git a/compose/types/locale.gen.go b/compose/types/locale.gen.go index a061d52ff..aaa6e824e 100644 --- a/compose/types/locale.gen.go +++ b/compose/types/locale.gen.go @@ -120,7 +120,7 @@ var ( // ResourceTranslation returns string representation of Locale resource for ModuleField by calling ModuleFieldResourceTranslation fn // -// Locale resource is in the compose:module-field/... format +// Locale resource is in "compose:module-field/..." format // // This function is auto-generated func (r ModuleField) ResourceTranslation() string { @@ -162,7 +162,7 @@ func (r *ModuleField) EncodeTranslations() (out locale.ResourceTranslationSet) { out = append(out, &locale.ResourceTranslation{ Resource: r.ResourceTranslation(), Key: LocaleKeyModuleFieldLabel.Path, - Msg: r.Label, + Msg: locale.SanitizeMessage(r.Label), }) } @@ -177,7 +177,7 @@ func (r *ModuleField) EncodeTranslations() (out locale.ResourceTranslationSet) { // ResourceTranslation returns string representation of Locale resource for Module by calling ModuleResourceTranslation fn // -// Locale resource is in the compose:module/... format +// Locale resource is in "compose:module/..." format // // This function is auto-generated func (r Module) ResourceTranslation() string { @@ -216,7 +216,7 @@ func (r *Module) EncodeTranslations() (out locale.ResourceTranslationSet) { out = append(out, &locale.ResourceTranslation{ Resource: r.ResourceTranslation(), Key: LocaleKeyModuleName.Path, - Msg: r.Name, + Msg: locale.SanitizeMessage(r.Name), }) } @@ -227,7 +227,7 @@ func (r *Module) EncodeTranslations() (out locale.ResourceTranslationSet) { // ResourceTranslation returns string representation of Locale resource for Namespace by calling NamespaceResourceTranslation fn // -// Locale resource is in the compose:namespace/... format +// Locale resource is in "compose:namespace/..." format // // This function is auto-generated func (r Namespace) ResourceTranslation() string { @@ -270,21 +270,21 @@ func (r *Namespace) EncodeTranslations() (out locale.ResourceTranslationSet) { out = append(out, &locale.ResourceTranslation{ Resource: r.ResourceTranslation(), Key: LocaleKeyNamespaceName.Path, - Msg: r.Name, + Msg: locale.SanitizeMessage(r.Name), }) } if r.Meta.Subtitle != "" { out = append(out, &locale.ResourceTranslation{ Resource: r.ResourceTranslation(), Key: LocaleKeyNamespaceSubtitle.Path, - Msg: r.Meta.Subtitle, + Msg: locale.SanitizeMessage(r.Meta.Subtitle), }) } if r.Meta.Description != "" { out = append(out, &locale.ResourceTranslation{ Resource: r.ResourceTranslation(), Key: LocaleKeyNamespaceDescription.Path, - Msg: r.Meta.Description, + Msg: locale.SanitizeMessage(r.Meta.Description), }) } @@ -293,7 +293,7 @@ func (r *Namespace) EncodeTranslations() (out locale.ResourceTranslationSet) { // ResourceTranslation returns string representation of Locale resource for Page by calling PageResourceTranslation fn // -// Locale resource is in the compose:page/... format +// Locale resource is in "compose:page/..." format // // This function is auto-generated func (r Page) ResourceTranslation() string { @@ -335,14 +335,14 @@ func (r *Page) EncodeTranslations() (out locale.ResourceTranslationSet) { out = append(out, &locale.ResourceTranslation{ Resource: r.ResourceTranslation(), Key: LocaleKeyPageTitle.Path, - Msg: r.Title, + Msg: locale.SanitizeMessage(r.Title), }) } if r.Description != "" { out = append(out, &locale.ResourceTranslation{ Resource: r.ResourceTranslation(), Key: LocaleKeyPageDescription.Path, - Msg: r.Description, + Msg: locale.SanitizeMessage(r.Description), }) } diff --git a/pkg/codegen-v3/assets/templates/gocode/locale/service.go.tpl b/pkg/codegen-v3/assets/templates/gocode/locale/service.go.tpl index 03d892764..20366955b 100644 --- a/pkg/codegen-v3/assets/templates/gocode/locale/service.go.tpl +++ b/pkg/codegen-v3/assets/templates/gocode/locale/service.go.tpl @@ -67,6 +67,10 @@ func (svc resourceTranslationsManager) Upsert(ctx context.Context, rr locale.Res } } + for _, r := range rr { + r.Msg = locale.SanitizeMessage(r.Msg) + } + // @todo validation me := intAuth.GetIdentityFromContext(ctx) @@ -209,3 +213,4 @@ func updateTranslations(ctx context.Context, ac localeAccessControl, lsvc Resour return nil } + diff --git a/pkg/codegen-v3/assets/templates/gocode/locale/types.go.tpl b/pkg/codegen-v3/assets/templates/gocode/locale/types.go.tpl index 2612b74e4..05be93e59 100644 --- a/pkg/codegen-v3/assets/templates/gocode/locale/types.go.tpl +++ b/pkg/codegen-v3/assets/templates/gocode/locale/types.go.tpl @@ -47,7 +47,7 @@ var ( // ResourceTranslation returns string representation of Locale resource for {{ .Resource }} by calling {{ .Resource }}ResourceTranslation fn // -// Locale resource is in the {{ .Locale.ResourceType }}/... format +// Locale resource is in "{{ .Locale.ResourceType }}/..." format // // This function is auto-generated func (r {{ .Resource }}) ResourceTranslation() string { @@ -112,7 +112,7 @@ func (r *{{ .Resource }}) EncodeTranslations() (out locale.ResourceTranslationSe out = append(out, &locale.ResourceTranslation{ Resource: r.ResourceTranslation(), Key: LocaleKey{{ $Resource }}{{coalesce (export .Name) (export .Path) }}.Path, - Msg: r.{{ .Field }}, + Msg: locale.SanitizeMessage(r.{{ .Field }}), }) } diff --git a/pkg/locale/input.go b/pkg/locale/input.go new file mode 100644 index 000000000..118eb8242 --- /dev/null +++ b/pkg/locale/input.go @@ -0,0 +1,15 @@ +package locale + +import ( + "html" + + "github.com/microcosm-cc/bluemonday" +) + +var ( + stripHtml = bluemonday.StripTagsPolicy().Sanitize +) + +func SanitizeMessage(in string) string { + return html.UnescapeString(stripHtml(in)) +} diff --git a/pkg/locale/input_test.go b/pkg/locale/input_test.go new file mode 100644 index 000000000..47238ad5d --- /dev/null +++ b/pkg/locale/input_test.go @@ -0,0 +1,27 @@ +package locale + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_SanitizeMessage(t *testing.T) { + tests := []struct { + name string + in string + out string + }{ + {"simple", "abc", "abc"}, + {"accents", "čšž", "čšž"}, + {"html", "čšž", "čšž"}, + {"broken html 1", "čšžčšžčšž"}, + {"broken html 3", "` and `<`. -func sanitizeHtml(s string) string { - const ( - htmlTagStart = 60 // Unicode `<` - htmlTagEnd = 62 // Unicode `>` - ) - - // Unescape string with any html tags - s = html.UnescapeString(s) - - // Set up a string builder and allocate enough memory for the new string. - var builder strings.Builder - builder.Grow(len(s) + utf8.UTFMax) - - in := false // True if we are inside an HTML tag. - start := 0 // The index of the previous start tag character `<` - end := 0 // The index of the previous end tag character `>` - - for i, c := range s { - // If this is the last character, and we are not in an HTML tag, save it. - if (i+1) == len(s) && end >= start { - builder.WriteString(s[end:]) - } - - // Keep going if the character is not `<` or `>` - if c != htmlTagStart && c != htmlTagEnd { - continue - } - - if c == htmlTagStart { - // Only update the start if we are not in a tag. - // This make sure we strip `<
` not just `
` - if !in { - start = i - } - in = true - - // Write the valid string between the close and start of the two tags. - builder.WriteString(s[end:start]) - continue - } - // else c == htmlTagEnd - in = false - end = i + 1 - } - s = builder.String() - return s -}