Securely update hidden props in the DSL that are only visible to the backend

This commit is contained in:
Max 2024-11-02 17:38:37 +08:00
parent c4e96366bf
commit 67de1c07cb
21 changed files with 277 additions and 23 deletions

View file

@ -1,4 +1,4 @@
package utils
package utils_test
import (
"testing"

View file

@ -6,6 +6,7 @@ import (
"github.com/yaoapp/yao/utils/fmt"
"github.com/yaoapp/yao/utils/json"
"github.com/yaoapp/yao/utils/str"
"github.com/yaoapp/yao/utils/throw"
"github.com/yaoapp/yao/utils/tree"
"github.com/yaoapp/yao/utils/url"
)
@ -15,6 +16,16 @@ func Init() {
process.Alias("xiang.helper.Captcha", "yao.utils.Captcha") // deprecated
process.Alias("xiang.helper.CaptchaValidate", "yao.utils.CaptchaValidate") // deprecated
// ****************************************
// * Processes Version 0.10.4+
// ****************************************
process.Register("utils.throw.Forbidden", throw.Forbidden)
process.Register("utils.throw.Unauthorized", throw.Unauthorized)
process.Register("utils.throw.NotFound", throw.NotFound)
process.Register("utils.throw.BadRequest", throw.BadRequest)
process.Register("utils.throw.InternalError", throw.InternalError)
process.Register("utils.throw.Exception", throw.Exception)
// ****************************************
// * Migrate Processes Version 0.10.2+
// ****************************************

View file

@ -1,4 +1,4 @@
package utils
package utils_test
import (
"fmt"

50
utils/throw/throw.go Normal file
View file

@ -0,0 +1,50 @@
package throw
import (
"github.com/yaoapp/gou/process"
"github.com/yaoapp/kun/exception"
)
// Unauthorized throw a unauthorized exception
func Unauthorized(process *process.Process) interface{} {
message := process.ArgsString(0, "Authentication required")
exception.New(message, 401).Throw()
return nil
}
// Forbidden throw a forbidden exception
func Forbidden(process *process.Process) interface{} {
message := process.ArgsString(0, "Access denied")
exception.New(message, 403).Throw()
return nil
}
// NotFound throw a not found exception
func NotFound(process *process.Process) interface{} {
message := process.ArgsString(0, "Resource not found")
exception.New(message, 404).Throw()
return nil
}
// BadRequest throw a bad request exception
func BadRequest(process *process.Process) interface{} {
message := process.ArgsString(0, "Bad Request")
exception.New(message, 400).Throw()
return nil
}
// InternalError throw a internal error exception
func InternalError(process *process.Process) interface{} {
message := process.ArgsString(0, "Internal Error")
exception.New(message, 500).Throw()
return nil
}
// Exception throw a exception
func Exception(process *process.Process) interface{} {
process.ValidateArgNums(2)
message := process.ArgsString(0)
code := process.ArgsInt(1)
exception.New(message, code).Throw()
return nil
}

57
utils/throw_test.go Normal file
View file

@ -0,0 +1,57 @@
package utils_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/yaoapp/gou/process"
"github.com/yaoapp/yao/utils"
)
func TestProcessUnauthorized(t *testing.T) {
utils.Init()
proc := process.New("utils.throw.Unauthorized", "Authentication required")
err := proc.Execute()
assert.NotNil(t, err)
assert.Equal(t, "Exception|401: Authentication required", err.Error())
}
func TestProcessForbidden(t *testing.T) {
utils.Init()
proc := process.New("utils.throw.Forbidden", "Access denied")
err := proc.Execute()
assert.NotNil(t, err)
assert.Equal(t, "Exception|403: Access denied", err.Error())
}
func TestProcessNotFound(t *testing.T) {
utils.Init()
proc := process.New("utils.throw.NotFound", "Resource not found")
err := proc.Execute()
assert.NotNil(t, err)
assert.Equal(t, "Exception|404: Resource not found", err.Error())
}
func TestProcessBadRequest(t *testing.T) {
utils.Init()
proc := process.New("utils.throw.BadRequest", "Bad Request")
err := proc.Execute()
assert.NotNil(t, err)
assert.Equal(t, "Exception|400: Bad Request", err.Error())
}
func TestProcessInternalError(t *testing.T) {
utils.Init()
proc := process.New("utils.throw.InternalError", "Internal Error")
err := proc.Execute()
assert.NotNil(t, err)
assert.Equal(t, "Exception|500: Internal Error", err.Error())
}
func TestProcessException(t *testing.T) {
utils.Init()
proc := process.New("utils.throw.Exception", "I'm a teapot", 418)
err := proc.Execute()
assert.NotNil(t, err)
assert.Equal(t, "Exception|418: I'm a teapot", err.Error())
}

View file

@ -1,4 +1,4 @@
package utils
package utils_test
import (
"testing"

View file

@ -1,4 +1,4 @@
package utils
package utils_test
import (
"net/url"
@ -7,10 +7,11 @@ import (
"github.com/stretchr/testify/assert"
"github.com/yaoapp/gou/process"
"github.com/yaoapp/gou/types"
"github.com/yaoapp/yao/utils"
)
func TestProcessParseQuery(t *testing.T) {
Init()
utils.Init()
args := []interface{}{"a=1&b=2&c=3&c=4"}
result, err := process.New("utils.url.ParseQuery", args...).Exec()
if err != nil {
@ -23,7 +24,7 @@ func TestProcessParseQuery(t *testing.T) {
}
func TestProcessParseURL(t *testing.T) {
Init()
utils.Init()
args := []interface{}{"http://www.google.com:8080/search?q=dotnet"}
result, err := process.New("utils.url.ParseURL", args...).Exec()
if err != nil {
@ -40,7 +41,7 @@ func TestProcessParseURL(t *testing.T) {
}
func TestProcessQueryParam(t *testing.T) {
Init()
utils.Init()
args := []interface{}{
map[string]interface{}{
"where.name.eq": "yao",

View file

@ -59,6 +59,8 @@ func (dsl *DSL) mapping() error {
if dsl.Fields.Filter != nil && dsl.Layout.Filter != nil && dsl.Layout.Filter.Columns != nil {
for _, inst := range dsl.Layout.Filter.Columns {
if filter, has := dsl.Fields.Filter[inst.Name]; has {
// Add the default value, and parse the backend only props
filter.Parse()
// Mapping ID
dsl.Mapping.Filters[filter.ID] = inst.Name
@ -79,6 +81,9 @@ func (dsl *DSL) mapping() error {
for _, inst := range dsl.Layout.Chart.Columns {
if field, has := dsl.Fields.Chart[inst.Name]; has {
// Add the default value, and parse the backend only props
field.Parse()
// Mapping ID
dsl.Mapping.Columns[field.ID] = inst.Name
dsl.Mapping.Columns[inst.Name] = field.ID

View file

@ -1,6 +1,8 @@
package component
import (
"strings"
jsoniter "github.com/json-iterator/go"
)
@ -10,6 +12,35 @@ import (
// yao.component.ImageView
// yao.component.UploadEdit
// BackendOnlyProps The components properties include visibility for backend only
var BackendOnlyProps = map[string]map[string]map[string]interface{}{
"select": {
"query": {
"xProps": map[string]interface{}{
"$remote": map[string]interface{}{"process": "yao.component.GetOptions"},
},
},
},
"autocomplete": {"query": {
"xProps": map[string]interface{}{
"$remote": map[string]interface{}{"process": "yao.component.GetOptions"},
},
}},
}
// DefaultProps The default properties for the component
var DefaultProps = map[string]map[string]map[string]interface{}{
"upload": {"api": {"$api": map[string]interface{}{"process": "fs.data.Upload"}}},
"image": {"api": {"$api": map[string]interface{}{"process": "utils.throw.Forbidden"}}}, // Just generate an effective URL, no need to upload
}
// UploadComponents the components that need to upload files
var UploadComponents = map[string]bool{
"upload": true,
"wangeditor": true,
"image": true,
}
// Export processes
func Export() error {
exportProcess()
@ -25,12 +56,7 @@ func (dsl DSL) MarshalJSON() ([]byte, error) {
func (dsl DSL) Map() map[string]interface{} {
res := map[string]interface{}{
"type": dsl.Type,
"props": map[string]interface{}(dsl.Props),
}
// Add Default Value for Upload api
if (dsl.Type == "Upload" || dsl.Type == "Image") && dsl.Props != nil && !dsl.Props.Has("api") {
res["props"].(map[string]interface{})["$api"] = map[string]interface{}{"process": "fs.data.Upload"}
"props": dsl.FontendProps(),
}
if dsl.HideLabel {
@ -43,6 +69,49 @@ func (dsl DSL) Map() map[string]interface{} {
return res
}
// FontendProps filter backend only properties
func (dsl DSL) FontendProps() map[string]interface{} {
if dsl.Props == nil {
return map[string]interface{}{}
}
props := map[string]interface{}{}
t := strings.ToLower(dsl.Type)
for key, val := range dsl.Props {
if BackendOnlyProps[t] != nil && BackendOnlyProps[t][key] != nil {
continue
}
props[key] = val
}
return props
}
// Parse the component properties
func (dsl *DSL) Parse() {
t := strings.ToLower(dsl.Type)
// Check if the component has default props
if dsl.Props != nil && DefaultProps[t] != nil {
for key, val := range DefaultProps[t] {
if !dsl.Props.Has(key) {
for k, v := range val {
dsl.Props[k] = v
}
}
}
}
// Check if the component has backend only props
if dsl.Props != nil && BackendOnlyProps[t] != nil {
for key, val := range BackendOnlyProps[t] {
if dsl.Props.Has(key) {
for k, v := range val {
dsl.Props[k] = v
}
}
}
}
}
// Clone Component
func (dsl *DSL) Clone() *DSL {
new := DSL{

View file

@ -9,13 +9,24 @@ import (
"github.com/yaoapp/gou/process"
"github.com/yaoapp/kun/any"
"github.com/yaoapp/kun/exception"
"github.com/yaoapp/kun/utils"
)
// Export process
func exportProcess() {
process.Register("yao.component.getoptions", processGetOptions)
process.Register("yao.component.selectoptions", processSelectOptions)
}
// processGetOptions get options
func processGetOptions(process *process.Process) interface{} {
utils.Dump(process.Args)
return []map[string]interface{}{
{"label": "Option 1", "value": "1"},
{"label": "Option 2", "value": "2"},
}
}
func processSelectOptions(process *process.Process) interface{} {
process.ValidateArgNums(1)
query := process.ArgsMap(0, map[string]interface{}{})

View file

@ -13,7 +13,12 @@ import (
// CloudProps parse CloudProps
func (p PropsDSL) CloudProps(xpath, component string) (map[string]CloudPropsDSL, error) {
return p.parseCloudProps(xpath, component, p)
if p == nil {
return nil, fmt.Errorf("props is required")
}
return p.parseCloudProps(xpath, component, p, p)
}
// Path api path
@ -42,18 +47,20 @@ func (cProp CloudPropsDSL) ExecUpload(process *gouProcess.Process, upload types.
}
// Create process
p, err := gouProcess.Of(name, upload)
p, err := gouProcess.Of(name, upload, cProp.Props)
if err != nil {
log.Error("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error())
return nil, fmt.Errorf("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error())
}
// Excute process
res, err := p.WithGlobal(process.Global).WithSID(process.Sid).Exec()
err = p.WithGlobal(process.Global).WithSID(process.Sid).Execute()
if err != nil {
log.Error("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error())
return nil, fmt.Errorf("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error())
}
defer p.Release()
res := p.Value()
return res, nil
}
@ -81,19 +88,21 @@ func (cProp CloudPropsDSL) ExecQuery(process *gouProcess.Process, query map[stri
}
// Create process
p, err := gouProcess.Of(name, query)
p, err := gouProcess.Of(name, query, cProp.Props)
if err != nil {
log.Error("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error())
return nil, fmt.Errorf("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error())
}
// Excute process
res, err := p.WithGlobal(process.Global).WithSID(process.Sid).Exec()
err = p.WithGlobal(process.Global).WithSID(process.Sid).Execute()
if err != nil {
log.Error("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error())
return nil, fmt.Errorf("[component] %s.$%s %s", cProp.Xpath, cProp.Name, err.Error())
}
defer p.Release()
res := p.Value()
return res, nil
}
@ -140,7 +149,7 @@ func (cProp CloudPropsDSL) replaceMap(data map[string]interface{}, root string,
return nil
}
func (p PropsDSL) parseCloudProps(xpath string, component string, props map[string]interface{}) (map[string]CloudPropsDSL, error) {
func (p PropsDSL) parseCloudProps(xpath string, component string, props map[string]interface{}, root map[string]interface{}) (map[string]CloudPropsDSL, error) {
res := map[string]CloudPropsDSL{}
@ -148,7 +157,7 @@ func (p PropsDSL) parseCloudProps(xpath string, component string, props map[stri
fullname := fmt.Sprintf("%s.%s", xpath, name)
if sub, ok := prop.(map[string]interface{}); ok {
cProps, err := p.parseCloudProps(fullname, component, sub)
cProps, err := p.parseCloudProps(fullname, component, sub, root)
if err != nil {
return nil, err
}
@ -165,6 +174,7 @@ func (p PropsDSL) parseCloudProps(xpath string, component string, props map[stri
Name: strings.TrimPrefix(name, "$"),
Type: component,
Xpath: xpath,
Props: root,
}
err := cProp.Parse(prop)

View file

@ -104,4 +104,5 @@ type CloudPropsDSL struct {
Name string `json:"name,omitempty"`
Process string `json:"process,omitempty"`
Query map[string]interface{} `json:"query,omitempty"`
Props map[string]interface{} `json:"props,omitempty"` // The original props
}

View file

@ -60,6 +60,9 @@ func (dsl *DSL) mapping() error {
for _, inst := range dsl.Layout.Filter.Columns {
if filter, has := dsl.Fields.Filter[inst.Name]; has {
// Add the default value, and parse the backend only props
filter.Parse()
// Mapping ID
dsl.Mapping.Filters[filter.ID] = inst.Name
dsl.Mapping.Filters[inst.Name] = filter.ID
@ -80,6 +83,9 @@ func (dsl *DSL) mapping() error {
if field, has := dsl.Fields.Dashboard[inst.Name]; has {
// Add the default value, and parse the backend only props
field.Parse()
// Mapping ID
dsl.Mapping.Columns[field.ID] = inst.Name
dsl.Mapping.Columns[inst.Name] = field.ID

View file

@ -87,6 +87,16 @@ func (column ColumnDSL) EditBind() string {
return column.Bind
}
// Parse the column dsl, add the default value, and parse the backend only props
func (column ColumnDSL) Parse() {
if column.View != nil {
column.View.Parse()
}
if column.Edit != nil {
column.Edit.Parse()
}
}
// Clone column
func (column *ColumnDSL) Clone() *ColumnDSL {
new := ColumnDSL{

View file

@ -27,6 +27,13 @@ func (filter *FilterDSL) UnmarshalJSON(data []byte) error {
return nil
}
// Parse the column dsl, add the default value, and parse the backend only props
func (filter FilterDSL) Parse() {
if filter.Edit != nil {
filter.Edit.Parse()
}
}
// Hash hash value
func (filter FilterDSL) Hash() (string, error) {
h := md4.New()

View file

@ -303,7 +303,8 @@ func (dsl *DSL) Xgen(data map[string]interface{}, excludes map[string]bool) (map
for _, cProp := range dsl.CProps {
err := cProp.Replace(setting, func(cProp component.CloudPropsDSL) interface{} {
if cProp.Type == "Upload" || cProp.Type == "WangEditor" {
t := strings.ToLower(cProp.Type)
if component.UploadComponents[t] {
return fmt.Sprintf("/api/__yao/form/%s%s", dsl.ID, cProp.UploadPath())
}

View file

@ -48,6 +48,10 @@ func (dsl *DSL) mapping() error {
dsl.Layout.listColumns(func(path string, inst Column) {
if field, has := dsl.Fields.Form[inst.Name]; has {
// Add the default value, and parse the backend only props
field.Parse()
// Mapping ID
dsl.Mapping.Columns[field.ID] = inst.Name
dsl.Mapping.Columns[inst.Name] = field.ID

View file

@ -261,7 +261,8 @@ func (dsl *DSL) Xgen(data map[string]interface{}, excludes map[string]bool, quer
cProp.Query = newQuery.(map[string]interface{})
}
if cProp.Type == "Upload" || cProp.Type == "WangEditor" {
t := strings.ToLower(cProp.Type)
if component.UploadComponents[t] {
return fmt.Sprintf("/api/__yao/list/%s%s", dsl.ID, cProp.UploadPath())
}

View file

@ -50,6 +50,9 @@ func (dsl *DSL) mapping() error {
if field, has := dsl.Fields.List[inst.Name]; has {
// Add the default value, and parse the backend only props
field.Parse()
// Mapping ID
dsl.Mapping.Columns[field.ID] = inst.Name
dsl.Mapping.Columns[inst.Name] = field.ID

View file

@ -65,6 +65,9 @@ func (dsl *DSL) mapping() error {
if filter, has := dsl.Fields.Filter[inst.Name]; has {
// Add the default value, and parse the backend only props
filter.Parse()
// Mapping ID
dsl.Mapping.Filters[filter.ID] = inst.Name
dsl.Mapping.Filters[inst.Name] = filter.ID
@ -85,6 +88,9 @@ func (dsl *DSL) mapping() error {
for _, inst := range dsl.Layout.Table.Columns {
if field, has := dsl.Fields.Table[inst.Name]; has {
// Add the default value, and parse the backend only props
field.Parse()
// Mapping ID
dsl.Mapping.Columns[field.ID] = inst.Name
dsl.Mapping.Columns[inst.Name] = field.ID

View file

@ -344,7 +344,8 @@ func (dsl *DSL) Xgen(data map[string]interface{}, excludes map[string]bool) (map
for _, cProp := range dsl.CProps {
err := cProp.Replace(setting, func(cProp component.CloudPropsDSL) interface{} {
if cProp.Type == "Upload" || cProp.Type == "WangEditor" || cProp.Type == "Image" {
t := strings.ToLower(cProp.Type)
if component.UploadComponents[t] {
return fmt.Sprintf("/api/__yao/table/%s%s", dsl.ID, cProp.UploadPath())
}