From 346cc4e557ad760621302a5c03338e931cc7a32f Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Tue, 18 May 2021 16:06:49 +0200 Subject: [PATCH 1/4] feat: add dynamic scope from request's input - all occurences of '[abc]' where 'abc' is a valid input name ('name' field from json) is replaced with its value between square brackets --- handler.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/handler.go b/handler.go index 95c478c..f4b104b 100644 --- a/handler.go +++ b/handler.go @@ -1,7 +1,9 @@ package aicra import ( + "fmt" "net/http" + "strings" "git.xdrm.io/go/aicra/api" "git.xdrm.io/go/aicra/internal/config" @@ -50,8 +52,26 @@ func (s Handler) resolve(w http.ResponseWriter, r *http.Request) { return } + // replace format '[a]' in scope where 'a' is an existing input's name + scope := make([][]string, len(service.Scope)) + for a, list := range service.Scope { + scope[a] = make([]string, len(list)) + for b, perm := range list { + scope[a][b] = perm + for name, value := range input.Data { + if stringer, ok := value.(fmt.Stringer); ok { + scope[a][b] = strings.ReplaceAll( + scope[a][b], + fmt.Sprintf("[%s]", name), + fmt.Sprintf("[%s]", stringer), + ) + } + } + } + } + var auth = api.Auth{ - Required: service.Scope, + Required: scope, Active: []string{}, } From 2a17ba2f72c040f87035f71c69f16b87407b2229 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Tue, 18 May 2021 16:24:31 +0200 Subject: [PATCH 2/4] fix: allow non-Stringer using %v format (unsafe but does the job) --- handler.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/handler.go b/handler.go index f4b104b..a4a3af1 100644 --- a/handler.go +++ b/handler.go @@ -59,13 +59,11 @@ func (s Handler) resolve(w http.ResponseWriter, r *http.Request) { for b, perm := range list { scope[a][b] = perm for name, value := range input.Data { - if stringer, ok := value.(fmt.Stringer); ok { - scope[a][b] = strings.ReplaceAll( - scope[a][b], - fmt.Sprintf("[%s]", name), - fmt.Sprintf("[%s]", stringer), - ) - } + scope[a][b] = strings.ReplaceAll( + scope[a][b], + fmt.Sprintf("[%s]", name), + fmt.Sprintf("[%v]", value), + ) } } } From 8c2ebd916e82b1154324296589650377598288c2 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Tue, 18 May 2021 16:30:20 +0200 Subject: [PATCH 3/4] feat: add test coverage --- handler_test.go | 276 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 273 insertions(+), 3 deletions(-) diff --git a/handler_test.go b/handler_test.go index a888a2e..6d0cfd8 100644 --- a/handler_test.go +++ b/handler_test.go @@ -1,4 +1,4 @@ -package aicra +package aicra_test import ( "bytes" @@ -9,11 +9,35 @@ import ( "strings" "testing" + "git.xdrm.io/go/aicra" "git.xdrm.io/go/aicra/api" + "git.xdrm.io/go/aicra/datatype/builtin" ) +func addBuiltinTypes(b *aicra.Builder) error { + if err := b.AddType(builtin.AnyDataType{}); err != nil { + return err + } + if err := b.AddType(builtin.BoolDataType{}); err != nil { + return err + } + if err := b.AddType(builtin.FloatDataType{}); err != nil { + return err + } + if err := b.AddType(builtin.IntDataType{}); err != nil { + return err + } + if err := b.AddType(builtin.StringDataType{}); err != nil { + return err + } + if err := b.AddType(builtin.UintDataType{}); err != nil { + return err + } + return nil +} + func TestWith(t *testing.T) { - builder := &Builder{} + builder := &aicra.Builder{} if err := addBuiltinTypes(builder); err != nil { t.Fatalf("unexpected error <%v>", err) } @@ -182,7 +206,7 @@ func TestWithAuth(t *testing.T) { for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - builder := &Builder{} + builder := &aicra.Builder{} if err := addBuiltinTypes(builder); err != nil { t.Fatalf("unexpected error <%v>", err) } @@ -239,3 +263,249 @@ func TestWithAuth(t *testing.T) { } } + +func TestDynamicScope(t *testing.T) { + tt := []struct { + name string + manifest string + path string + handler interface{} + url string + body string + permissions []string + granted bool + }{ + { + name: "replace one granted", + manifest: `[ + { + "method": "POST", + "path": "/path/{id}", + "info": "info", + "scope": [["user[Input1]"]], + "in": { + "{id}": { "info": "info", "name": "Input1", "type": "uint" } + }, + "out": {} + } + ]`, + path: "/path/{id}", + handler: func(struct{ Input1 uint }) (*struct{}, api.Err) { return nil, api.ErrSuccess }, + url: "/path/123", + body: ``, + permissions: []string{"user[123]"}, + granted: true, + }, + { + name: "replace one mismatch", + manifest: `[ + { + "method": "POST", + "path": "/path/{id}", + "info": "info", + "scope": [["user[Input1]"]], + "in": { + "{id}": { "info": "info", "name": "Input1", "type": "uint" } + }, + "out": {} + } + ]`, + path: "/path/{id}", + handler: func(struct{ Input1 uint }) (*struct{}, api.Err) { return nil, api.ErrSuccess }, + url: "/path/666", + body: ``, + permissions: []string{"user[123]"}, + granted: false, + }, + { + name: "replace one valid dot separated", + manifest: `[ + { + "method": "POST", + "path": "/path/{id}", + "info": "info", + "scope": [["prefix.user[User].suffix"]], + "in": { + "{id}": { "info": "info", "name": "User", "type": "uint" } + }, + "out": {} + } + ]`, + path: "/path/{id}", + handler: func(struct{ User uint }) (*struct{}, api.Err) { return nil, api.ErrSuccess }, + url: "/path/123", + body: ``, + permissions: []string{"prefix.user[123].suffix"}, + granted: true, + }, + { + name: "replace two valid dot separated", + manifest: `[ + { + "method": "POST", + "path": "/prefix/{pid}/user/{uid}", + "info": "info", + "scope": [["prefix[Prefix].user[User].suffix"]], + "in": { + "{pid}": { "info": "info", "name": "Prefix", "type": "uint" }, + "{uid}": { "info": "info", "name": "User", "type": "uint" } + }, + "out": {} + } + ]`, + path: "/prefix/{pid}/user/{uid}", + handler: func(struct { + Prefix uint + User uint + }) (*struct{}, api.Err) { + return nil, api.ErrSuccess + }, + url: "/prefix/123/user/456", + body: ``, + permissions: []string{"prefix[123].user[456].suffix"}, + granted: true, + }, + { + name: "replace two invalid dot separated", + manifest: `[ + { + "method": "POST", + "path": "/prefix/{pid}/user/{uid}", + "info": "info", + "scope": [["prefix[Prefix].user[User].suffix"]], + "in": { + "{pid}": { "info": "info", "name": "Prefix", "type": "uint" }, + "{uid}": { "info": "info", "name": "User", "type": "uint" } + }, + "out": {} + } + ]`, + path: "/prefix/{pid}/user/{uid}", + handler: func(struct { + Prefix uint + User uint + }) (*struct{}, api.Err) { + return nil, api.ErrSuccess + }, + url: "/prefix/123/user/666", + body: ``, + permissions: []string{"prefix[123].user[456].suffix"}, + granted: false, + }, + { + name: "replace three valid dot separated", + manifest: `[ + { + "method": "POST", + "path": "/prefix/{pid}/user/{uid}/suffix/{sid}", + "info": "info", + "scope": [["prefix[Prefix].user[User].suffix[Suffix]"]], + "in": { + "{pid}": { "info": "info", "name": "Prefix", "type": "uint" }, + "{uid}": { "info": "info", "name": "User", "type": "uint" }, + "{sid}": { "info": "info", "name": "Suffix", "type": "uint" } + }, + "out": {} + } + ]`, + path: "/prefix/{pid}/user/{uid}/suffix/{sid}", + handler: func(struct { + Prefix uint + User uint + Suffix uint + }) (*struct{}, api.Err) { + return nil, api.ErrSuccess + }, + url: "/prefix/123/user/456/suffix/789", + body: ``, + permissions: []string{"prefix[123].user[456].suffix[789]"}, + granted: true, + }, + { + name: "replace three invalid dot separated", + manifest: `[ + { + "method": "POST", + "path": "/prefix/{pid}/user/{uid}/suffix/{sid}", + "info": "info", + "scope": [["prefix[Prefix].user[User].suffix[Suffix]"]], + "in": { + "{pid}": { "info": "info", "name": "Prefix", "type": "uint" }, + "{uid}": { "info": "info", "name": "User", "type": "uint" }, + "{sid}": { "info": "info", "name": "Suffix", "type": "uint" } + }, + "out": {} + } + ]`, + path: "/prefix/{pid}/user/{uid}/suffix/{sid}", + handler: func(struct { + Prefix uint + User uint + Suffix uint + }) (*struct{}, api.Err) { + return nil, api.ErrSuccess + }, + url: "/prefix/123/user/666/suffix/789", + body: ``, + permissions: []string{"prefix[123].user[456].suffix[789]"}, + granted: false, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + builder := &aicra.Builder{} + if err := addBuiltinTypes(builder); err != nil { + t.Fatalf("unexpected error <%v>", err) + } + + // tester middleware (last executed) + builder.WithAuth(func(next api.AuthHandlerFunc) api.AuthHandlerFunc { + return func(a api.Auth, w http.ResponseWriter, r *http.Request) { + if a.Granted() == tc.granted { + return + } + if a.Granted() { + t.Fatalf("unexpected granted auth") + } else { + t.Fatalf("expected granted auth") + } + } + }) + + // update permissions + builder.WithAuth(func(next api.AuthHandlerFunc) api.AuthHandlerFunc { + return func(a api.Auth, w http.ResponseWriter, r *http.Request) { + a.Active = tc.permissions + next(a, w, r) + } + }) + + err := builder.Setup(strings.NewReader(tc.manifest)) + if err != nil { + t.Fatalf("setup: unexpected error <%v>", err) + } + + if err := builder.Bind(http.MethodPost, tc.path, tc.handler); err != nil { + t.Fatalf("bind: unexpected error <%v>", err) + } + + handler, err := builder.Build() + if err != nil { + t.Fatalf("build: unexpected error <%v>", err) + } + + response := httptest.NewRecorder() + body := strings.NewReader(tc.body) + request := httptest.NewRequest(http.MethodPost, tc.url, body) + + // test request + handler.ServeHTTP(response, request) + if response.Body == nil { + t.Fatalf("response has no body") + } + + }) + } + +} From c35e2fdd9a1f5bf2a611414783e8576d27ae6d03 Mon Sep 17 00:00:00 2001 From: xdrm-brackets Date: Tue, 18 May 2021 17:45:07 +0200 Subject: [PATCH 4/4] fix: do not use optional (nil) inputs for dynamic scope --- handler.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/handler.go b/handler.go index a4a3af1..87ce076 100644 --- a/handler.go +++ b/handler.go @@ -59,11 +59,14 @@ func (s Handler) resolve(w http.ResponseWriter, r *http.Request) { for b, perm := range list { scope[a][b] = perm for name, value := range input.Data { - scope[a][b] = strings.ReplaceAll( - scope[a][b], - fmt.Sprintf("[%s]", name), - fmt.Sprintf("[%v]", value), + var ( + token = fmt.Sprintf("[%s]", name) + replacement = "" ) + if value != nil { + replacement = fmt.Sprintf("[%v]", value) + } + scope[a][b] = strings.ReplaceAll(scope[a][b], token, replacement) } } }