diff --git a/handler.go b/handler.go index 95c478c..87ce076 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,27 @@ 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 { + 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) + } + } + } + var auth = api.Auth{ - Required: service.Scope, + Required: scope, Active: []string{}, } 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") + } + + }) + } + +}