Compare commits
No commits in common. "214e2348aa9339b92054ab9f9f1be77abe089305" and "976b13bd38f372cc2be7f8a69854a5054cb94290" have entirely different histories.
214e2348aa
...
976b13bd38
|
@ -4,10 +4,3 @@ import "net/http"
|
||||||
|
|
||||||
// Adapter to encapsulate incoming requests
|
// Adapter to encapsulate incoming requests
|
||||||
type Adapter func(http.HandlerFunc) http.HandlerFunc
|
type Adapter func(http.HandlerFunc) http.HandlerFunc
|
||||||
|
|
||||||
// AuthHandlerFunc is http.HandlerFunc with additional Authorization information
|
|
||||||
type AuthHandlerFunc func(Auth, http.ResponseWriter, *http.Request)
|
|
||||||
|
|
||||||
// AuthAdapter to encapsulate incoming request with access to api.Auth
|
|
||||||
// to manage permissions
|
|
||||||
type AuthAdapter func(AuthHandlerFunc) AuthHandlerFunc
|
|
||||||
|
|
62
api/auth.go
62
api/auth.go
|
@ -1,62 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
// Auth can be used by http middleware to
|
|
||||||
// 1) consult required roles in @Auth.Required
|
|
||||||
// 2) update active roles in @Auth.Active
|
|
||||||
type Auth struct {
|
|
||||||
// required roles for this request
|
|
||||||
// - the first dimension of the array reads as a OR
|
|
||||||
// - the second dimension reads as a AND
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
// [ [A, B], [C, D] ] reads: roles (A and B) or (C and D) are required
|
|
||||||
//
|
|
||||||
// Warning: must not be mutated
|
|
||||||
Required [][]string
|
|
||||||
|
|
||||||
// active roles to be updated by authentication
|
|
||||||
// procedures (e.g. jwt)
|
|
||||||
Active []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Granted returns whether the authorization is granted
|
|
||||||
// i.e. Auth.Active fulfills Auth.Required
|
|
||||||
func (a Auth) Granted() bool {
|
|
||||||
var nothingRequired = true
|
|
||||||
|
|
||||||
// first dimension: OR ; at least one is valid
|
|
||||||
for _, required := range a.Required {
|
|
||||||
// empty list
|
|
||||||
if len(required) < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
nothingRequired = false
|
|
||||||
|
|
||||||
// second dimension: AND ; all required must be fulfilled
|
|
||||||
if a.fulfills(required) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nothingRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns whether Auth.Active fulfills (contains) all @required roles
|
|
||||||
func (a Auth) fulfills(required []string) bool {
|
|
||||||
for _, requiredRole := range required {
|
|
||||||
var found = false
|
|
||||||
for _, activeRole := range a.Active {
|
|
||||||
if activeRole == requiredRole {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// missing role -> fail
|
|
||||||
if !found {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// all @required are fulfilled
|
|
||||||
return true
|
|
||||||
}
|
|
108
api/auth_test.go
108
api/auth_test.go
|
@ -1,108 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCombination(t *testing.T) {
|
|
||||||
tcases := []struct {
|
|
||||||
Name string
|
|
||||||
Required [][]string
|
|
||||||
Active []string
|
|
||||||
Granted bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Name: "no requirement none given",
|
|
||||||
Required: [][]string{},
|
|
||||||
Active: []string{},
|
|
||||||
Granted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "no requirement 1 given",
|
|
||||||
Required: [][]string{},
|
|
||||||
Active: []string{"a"},
|
|
||||||
Granted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "no requirement some given",
|
|
||||||
Required: [][]string{},
|
|
||||||
Active: []string{"a", "b"},
|
|
||||||
Granted: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
Name: "1 required none given",
|
|
||||||
Required: [][]string{{"a"}},
|
|
||||||
Active: []string{},
|
|
||||||
Granted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "1 required fulfilled",
|
|
||||||
Required: [][]string{{"a"}},
|
|
||||||
Active: []string{"a"},
|
|
||||||
Granted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "1 required mismatch",
|
|
||||||
Required: [][]string{{"a"}},
|
|
||||||
Active: []string{"b"},
|
|
||||||
Granted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "2 required none gien",
|
|
||||||
Required: [][]string{{"a", "b"}},
|
|
||||||
Active: []string{},
|
|
||||||
Granted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "2 required other given",
|
|
||||||
Required: [][]string{{"a", "b"}},
|
|
||||||
Active: []string{"c"},
|
|
||||||
Granted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "2 required one given",
|
|
||||||
Required: [][]string{{"a", "b"}},
|
|
||||||
Active: []string{"a"},
|
|
||||||
Granted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "2 required fulfilled",
|
|
||||||
Required: [][]string{{"a", "b"}},
|
|
||||||
Active: []string{"a", "b"},
|
|
||||||
Granted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "2 or 2 required first fulfilled",
|
|
||||||
Required: [][]string{{"a", "b"}, {"c", "d"}},
|
|
||||||
Active: []string{"a", "b"},
|
|
||||||
Granted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "2 or 2 required second fulfilled",
|
|
||||||
Required: [][]string{{"a", "b"}, {"c", "d"}},
|
|
||||||
Active: []string{"c", "d"},
|
|
||||||
Granted: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tcase := range tcases {
|
|
||||||
t.Run(tcase.Name, func(t *testing.T) {
|
|
||||||
|
|
||||||
auth := Auth{
|
|
||||||
Required: tcase.Required,
|
|
||||||
Active: tcase.Active,
|
|
||||||
}
|
|
||||||
|
|
||||||
// all right
|
|
||||||
if tcase.Granted == auth.Granted() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if tcase.Granted && !auth.Granted() {
|
|
||||||
t.Fatalf("expected granted authorization")
|
|
||||||
}
|
|
||||||
t.Fatalf("unexpected granted authorization")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
16
builder.go
16
builder.go
|
@ -16,7 +16,6 @@ type Builder struct {
|
||||||
conf *config.Server
|
conf *config.Server
|
||||||
handlers []*apiHandler
|
handlers []*apiHandler
|
||||||
adapters []api.Adapter
|
adapters []api.Adapter
|
||||||
authAdapters []api.AuthAdapter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// represents an api handler (method-pattern combination)
|
// represents an api handler (method-pattern combination)
|
||||||
|
@ -41,8 +40,8 @@ func (b *Builder) AddType(t datatype.T) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// With adds an http adapter (middleware)
|
// Use adds an http adapter (middleware)
|
||||||
func (b *Builder) With(adapter api.Adapter) {
|
func (b *Builder) Use(adapter api.Adapter) {
|
||||||
if b.conf == nil {
|
if b.conf == nil {
|
||||||
b.conf = &config.Server{}
|
b.conf = &config.Server{}
|
||||||
}
|
}
|
||||||
|
@ -52,17 +51,6 @@ func (b *Builder) With(adapter api.Adapter) {
|
||||||
b.adapters = append(b.adapters, adapter)
|
b.adapters = append(b.adapters, adapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithAuth adds an http adapter with auth capabilities (middleware)
|
|
||||||
func (b *Builder) WithAuth(adapter api.AuthAdapter) {
|
|
||||||
if b.conf == nil {
|
|
||||||
b.conf = &config.Server{}
|
|
||||||
}
|
|
||||||
if b.authAdapters == nil {
|
|
||||||
b.authAdapters = make([]api.AuthAdapter, 0)
|
|
||||||
}
|
|
||||||
b.authAdapters = append(b.authAdapters, adapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup the builder with its api definition file
|
// Setup the builder with its api definition file
|
||||||
// panics if already setup
|
// panics if already setup
|
||||||
func (b *Builder) Setup(r io.Reader) error {
|
func (b *Builder) Setup(r io.Reader) error {
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
package aicra
|
package aicra
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -48,6 +52,92 @@ func TestAddType(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUse(t *testing.T) {
|
||||||
|
builder := &Builder{}
|
||||||
|
if err := addBuiltinTypes(builder); err != nil {
|
||||||
|
t.Fatalf("unexpected error <%v>", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// build @n middlewares that take data from context and increment it
|
||||||
|
n := 1024
|
||||||
|
|
||||||
|
type ckey int
|
||||||
|
const key ckey = 0
|
||||||
|
|
||||||
|
middleware := func(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
newr := r
|
||||||
|
|
||||||
|
// first time -> store 1
|
||||||
|
value := r.Context().Value(key)
|
||||||
|
if value == nil {
|
||||||
|
newr = r.WithContext(context.WithValue(r.Context(), key, int(1)))
|
||||||
|
next(w, newr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get value and increment
|
||||||
|
cast, ok := value.(int)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("value is not an int")
|
||||||
|
}
|
||||||
|
cast++
|
||||||
|
newr = r.WithContext(context.WithValue(r.Context(), key, cast))
|
||||||
|
next(w, newr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add middleware @n times
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
builder.Use(middleware)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := strings.NewReader(`[ { "method": "GET", "path": "/path", "scope": [[]], "info": "info", "in": {}, "out": {} } ]`)
|
||||||
|
err := builder.Setup(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("setup: unexpected error <%v>", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pathHandler := func(ctx api.Ctx) (*struct{}, api.Err) {
|
||||||
|
// write value from middlewares into response
|
||||||
|
value := ctx.Req.Context().Value(key)
|
||||||
|
if value == nil {
|
||||||
|
t.Fatalf("nothing found in context")
|
||||||
|
}
|
||||||
|
cast, ok := value.(int)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("cannot cast context data to int")
|
||||||
|
}
|
||||||
|
// write to response
|
||||||
|
ctx.Res.Write([]byte(fmt.Sprintf("#%d#", cast)))
|
||||||
|
|
||||||
|
return nil, api.ErrSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := builder.Bind(http.MethodGet, "/path", pathHandler); 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()
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/path", &bytes.Buffer{})
|
||||||
|
|
||||||
|
// test request
|
||||||
|
handler.ServeHTTP(response, request)
|
||||||
|
if response.Body == nil {
|
||||||
|
t.Fatalf("response has no body")
|
||||||
|
}
|
||||||
|
token := fmt.Sprintf("#%d#", n)
|
||||||
|
if !strings.Contains(response.Body.String(), token) {
|
||||||
|
t.Fatalf("expected '%s' to be in response <%s>", token, response.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestBind(t *testing.T) {
|
func TestBind(t *testing.T) {
|
||||||
tcases := []struct {
|
tcases := []struct {
|
||||||
Name string
|
Name string
|
||||||
|
|
27
handler.go
27
handler.go
|
@ -13,7 +13,7 @@ type Handler Builder
|
||||||
|
|
||||||
// ServeHTTP implements http.Handler and wraps it in middlewares (adapters)
|
// ServeHTTP implements http.Handler and wraps it in middlewares (adapters)
|
||||||
func (s Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
var h = http.HandlerFunc(s.resolve)
|
var h = http.HandlerFunc(s.handleRequest)
|
||||||
|
|
||||||
for _, adapter := range s.adapters {
|
for _, adapter := range s.adapters {
|
||||||
h = adapter(h)
|
h = adapter(h)
|
||||||
|
@ -21,7 +21,7 @@ func (s Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
h(w, r)
|
h(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Handler) resolve(w http.ResponseWriter, r *http.Request) {
|
func (s Handler) handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
// 1. find a matching service from config
|
// 1. find a matching service from config
|
||||||
var service = s.conf.Find(r)
|
var service = s.conf.Find(r)
|
||||||
if service == nil {
|
if service == nil {
|
||||||
|
@ -50,29 +50,6 @@ func (s Handler) resolve(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var auth = api.Auth{
|
|
||||||
Required: service.Scope,
|
|
||||||
Active: []string{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. run auth-aware middlewares
|
|
||||||
var h = api.AuthHandlerFunc(func(a api.Auth, w http.ResponseWriter, r *http.Request) {
|
|
||||||
if !a.Granted() {
|
|
||||||
handleError(api.ErrPermission, w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.handle(input, handler, service, w, r)
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, adapter := range s.authAdapters {
|
|
||||||
h = adapter(h)
|
|
||||||
}
|
|
||||||
h(auth, w, r)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Handler) handle(input *reqdata.T, handler *apiHandler, service *config.Service, w http.ResponseWriter, r *http.Request) {
|
|
||||||
// 5. pass execution to the handler
|
// 5. pass execution to the handler
|
||||||
ctx := api.Ctx{Res: w, Req: r}
|
ctx := api.Ctx{Res: w, Req: r}
|
||||||
var outData, outErr = handler.dyn.Handle(ctx, input.Data)
|
var outData, outErr = handler.dyn.Handle(ctx, input.Data)
|
||||||
|
|
241
handler_test.go
241
handler_test.go
|
@ -1,241 +0,0 @@
|
||||||
package aicra
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.xdrm.io/go/aicra/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWith(t *testing.T) {
|
|
||||||
builder := &Builder{}
|
|
||||||
if err := addBuiltinTypes(builder); err != nil {
|
|
||||||
t.Fatalf("unexpected error <%v>", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// build @n middlewares that take data from context and increment it
|
|
||||||
n := 1024
|
|
||||||
|
|
||||||
type ckey int
|
|
||||||
const key ckey = 0
|
|
||||||
|
|
||||||
middleware := func(next http.HandlerFunc) http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
newr := r
|
|
||||||
|
|
||||||
// first time -> store 1
|
|
||||||
value := r.Context().Value(key)
|
|
||||||
if value == nil {
|
|
||||||
newr = r.WithContext(context.WithValue(r.Context(), key, int(1)))
|
|
||||||
next(w, newr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get value and increment
|
|
||||||
cast, ok := value.(int)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("value is not an int")
|
|
||||||
}
|
|
||||||
cast++
|
|
||||||
newr = r.WithContext(context.WithValue(r.Context(), key, cast))
|
|
||||||
next(w, newr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add middleware @n times
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
builder.With(middleware)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := strings.NewReader(`[ { "method": "GET", "path": "/path", "scope": [[]], "info": "info", "in": {}, "out": {} } ]`)
|
|
||||||
err := builder.Setup(config)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("setup: unexpected error <%v>", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pathHandler := func(ctx api.Ctx) (*struct{}, api.Err) {
|
|
||||||
// write value from middlewares into response
|
|
||||||
value := ctx.Req.Context().Value(key)
|
|
||||||
if value == nil {
|
|
||||||
t.Fatalf("nothing found in context")
|
|
||||||
}
|
|
||||||
cast, ok := value.(int)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("cannot cast context data to int")
|
|
||||||
}
|
|
||||||
// write to response
|
|
||||||
ctx.Res.Write([]byte(fmt.Sprintf("#%d#", cast)))
|
|
||||||
|
|
||||||
return nil, api.ErrSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := builder.Bind(http.MethodGet, "/path", pathHandler); 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()
|
|
||||||
request := httptest.NewRequest(http.MethodGet, "/path", &bytes.Buffer{})
|
|
||||||
|
|
||||||
// test request
|
|
||||||
handler.ServeHTTP(response, request)
|
|
||||||
if response.Body == nil {
|
|
||||||
t.Fatalf("response has no body")
|
|
||||||
}
|
|
||||||
token := fmt.Sprintf("#%d#", n)
|
|
||||||
if !strings.Contains(response.Body.String(), token) {
|
|
||||||
t.Fatalf("expected '%s' to be in response <%s>", token, response.Body.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithAuth(t *testing.T) {
|
|
||||||
|
|
||||||
tt := []struct {
|
|
||||||
name string
|
|
||||||
manifest string
|
|
||||||
permissions []string
|
|
||||||
granted bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "provide only requirement A",
|
|
||||||
manifest: `[ { "method": "GET", "path": "/path", "scope": [["A"]], "info": "info", "in": {}, "out": {} } ]`,
|
|
||||||
permissions: []string{"A"},
|
|
||||||
granted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing requirement",
|
|
||||||
manifest: `[ { "method": "GET", "path": "/path", "scope": [["A"]], "info": "info", "in": {}, "out": {} } ]`,
|
|
||||||
permissions: []string{},
|
|
||||||
granted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing requirements",
|
|
||||||
manifest: `[ { "method": "GET", "path": "/path", "scope": [["A", "B"]], "info": "info", "in": {}, "out": {} } ]`,
|
|
||||||
permissions: []string{},
|
|
||||||
granted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing some requirements",
|
|
||||||
manifest: `[ { "method": "GET", "path": "/path", "scope": [["A", "B"]], "info": "info", "in": {}, "out": {} } ]`,
|
|
||||||
permissions: []string{"A"},
|
|
||||||
granted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "provide requirements",
|
|
||||||
manifest: `[ { "method": "GET", "path": "/path", "scope": [["A", "B"]], "info": "info", "in": {}, "out": {} } ]`,
|
|
||||||
permissions: []string{"A", "B"},
|
|
||||||
granted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing OR requirements",
|
|
||||||
manifest: `[ { "method": "GET", "path": "/path", "scope": [["A"], ["B"]], "info": "info", "in": {}, "out": {} } ]`,
|
|
||||||
permissions: []string{"C"},
|
|
||||||
granted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "provide 1 OR requirement",
|
|
||||||
manifest: `[ { "method": "GET", "path": "/path", "scope": [["A"], ["B"]], "info": "info", "in": {}, "out": {} } ]`,
|
|
||||||
permissions: []string{"A"},
|
|
||||||
granted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "provide both OR requirements",
|
|
||||||
manifest: `[ { "method": "GET", "path": "/path", "scope": [["A"], ["B"]], "info": "info", "in": {}, "out": {} } ]`,
|
|
||||||
permissions: []string{"A", "B"},
|
|
||||||
granted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing composite OR requirements",
|
|
||||||
manifest: `[ { "method": "GET", "path": "/path", "scope": [["A", "B"], ["C", "D"]], "info": "info", "in": {}, "out": {} } ]`,
|
|
||||||
permissions: []string{},
|
|
||||||
granted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing partial composite OR requirements",
|
|
||||||
manifest: `[ { "method": "GET", "path": "/path", "scope": [["A", "B"], ["C", "D"]], "info": "info", "in": {}, "out": {} } ]`,
|
|
||||||
permissions: []string{"A", "C"},
|
|
||||||
granted: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "provide 1 composite OR requirement",
|
|
||||||
manifest: `[ { "method": "GET", "path": "/path", "scope": [["A", "B"], ["C", "D"]], "info": "info", "in": {}, "out": {} } ]`,
|
|
||||||
permissions: []string{"A", "B", "C"},
|
|
||||||
granted: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "provide both composite OR requirements",
|
|
||||||
manifest: `[ { "method": "GET", "path": "/path", "scope": [["A", "B"], ["C", "D"]], "info": "info", "in": {}, "out": {} } ]`,
|
|
||||||
permissions: []string{"A", "B", "C", "D"},
|
|
||||||
granted: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tt {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
builder := &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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
pathHandler := func(ctx api.Ctx) (*struct{}, api.Err) {
|
|
||||||
return nil, api.ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := builder.Bind(http.MethodGet, "/path", pathHandler); 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()
|
|
||||||
request := httptest.NewRequest(http.MethodGet, "/path", &bytes.Buffer{})
|
|
||||||
|
|
||||||
// test request
|
|
||||||
handler.ServeHTTP(response, request)
|
|
||||||
if response.Body == nil {
|
|
||||||
t.Fatalf("response has no body")
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue