Additional tests to ensure that schema introspection is working as expected

This commit is contained in:
2024-12-06 12:03:37 +00:00
parent ed3966e577
commit e54bbe8249
3 changed files with 400 additions and 171 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ var (
func prepareQueriesAndExemptions() { func prepareQueriesAndExemptions() {
for _, q := range cfg.Security.IntrospectionAllowed { for _, q := range cfg.Security.IntrospectionAllowed {
introspectionAllowedQueries[strings.ToLower(q)] = struct{}{} introspectionAllowedQueries[strings.ToLower(strings.TrimSpace(q))] = struct{}{}
} }
for _, u := range cfg.Server.AllowURLs { for _, u := range cfg.Server.AllowURLs {
+107
View File
@@ -3,9 +3,12 @@ package main
import ( import (
"fmt" "fmt"
"strings" "strings"
"testing"
"github.com/goccy/go-json" "github.com/goccy/go-json"
fiber "github.com/gofiber/fiber/v2" fiber "github.com/gofiber/fiber/v2"
"github.com/graphql-go/graphql/language/ast"
"github.com/graphql-go/graphql/language/parser"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )
@@ -502,3 +505,107 @@ func (suite *Tests) Test_DeepIntrospectionQueries() {
}) })
} }
} }
func TestIntrospectionQueryHandling(t *testing.T) {
tests := []struct {
name string
blockIntrospection bool
allowedQueries []string
query string
wantBlocked bool
}{
{
name: "allows __typename when in allowed list",
blockIntrospection: true,
allowedQueries: []string{"__typename"},
query: `{
users {
id
name
__typename
}
}`,
wantBlocked: false,
},
{
name: "case insensitive matching for allowed queries",
blockIntrospection: true,
allowedQueries: []string{"__TYPENAME"},
query: `{
users {
__typename
}
}`,
wantBlocked: false,
},
{
name: "blocks other introspection queries",
blockIntrospection: true,
allowedQueries: []string{"__typename"},
query: `{
__schema {
types {
name
}
}
}`,
wantBlocked: true,
},
{
name: "allows multiple __typename occurrences",
blockIntrospection: true,
allowedQueries: []string{"__typename"},
query: `{
users {
__typename
posts {
__typename
}
}
}`,
wantBlocked: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Setup config
cfg = &config{
Security: struct {
IntrospectionAllowed []string
BlockIntrospection bool
}{
IntrospectionAllowed: tt.allowedQueries,
BlockIntrospection: tt.blockIntrospection,
},
}
// Initialize allowed queries
prepareQueriesAndExemptions()
// Parse query
p, err := parser.Parse(parser.ParseParams{Source: tt.query})
if err != nil {
t.Fatalf("failed to parse query: %v", err)
}
// Create mock fiber context
app := fiber.New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx)
// Check selections
var blocked bool
for _, def := range p.Definitions {
if op, ok := def.(*ast.OperationDefinition); ok {
blocked = checkSelections(ctx, op.GetSelectionSet().Selections)
break
}
}
if blocked != tt.wantBlocked {
t.Errorf("checkSelections() blocked = %v, want %v", blocked, tt.wantBlocked)
}
})
}
}
+122
View File
@@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"os" "os"
"testing" "testing"
"time" "time"
@@ -11,6 +12,7 @@ import (
libpack_logging "github.com/lukaszraczylo/graphql-monitoring-proxy/logging" libpack_logging "github.com/lukaszraczylo/graphql-monitoring-proxy/logging"
assertions "github.com/stretchr/testify/assert" assertions "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/valyala/fasthttp"
) )
type Tests struct { type Tests struct {
@@ -138,3 +140,123 @@ func (suite *Tests) Test_getDetailsFromEnv() {
}) })
} }
} }
func TestIntrospectionEnvironmentConfig(t *testing.T) {
// Save original env vars
oldEnv := make(map[string]string)
varsToSave := []string{
"BLOCK_SCHEMA_INTROSPECTION",
"ALLOWED_INTROSPECTION",
"GMP_BLOCK_SCHEMA_INTROSPECTION",
"GMP_ALLOWED_INTROSPECTION",
}
for _, env := range varsToSave {
if val, exists := os.LookupEnv(env); exists {
oldEnv[env] = val
os.Unsetenv(env)
}
}
defer func() {
// Restore original env vars
for k, v := range oldEnv {
os.Setenv(k, v)
}
}()
tests := []struct {
name string
envVars map[string]string
query string
wantBlocked bool
wantEndpoint string
}{
{
name: "basic typename allowed",
envVars: map[string]string{
"BLOCK_SCHEMA_INTROSPECTION": "true",
"ALLOWED_INTROSPECTION": "__typename",
},
query: `{
users {
id
__typename
}
}`,
wantBlocked: false,
},
{
name: "GMP prefix takes precedence",
envVars: map[string]string{
"BLOCK_SCHEMA_INTROSPECTION": "false",
"GMP_BLOCK_SCHEMA_INTROSPECTION": "true",
"ALLOWED_INTROSPECTION": "__type",
"GMP_ALLOWED_INTROSPECTION": "__typename",
},
query: `{
users {
__typename
}
}`,
wantBlocked: false,
},
{
name: "multiple allowed queries",
envVars: map[string]string{
"BLOCK_SCHEMA_INTROSPECTION": "true",
"ALLOWED_INTROSPECTION": "__typename,__schema",
},
query: `{
__schema {
types {
name
__typename
}
}
}`,
wantBlocked: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set test env vars
for k, v := range tt.envVars {
os.Setenv(k, v)
}
// Reset global config
cfg = nil
parseConfig()
// Create test request
app := fiber.New()
ctx := app.AcquireCtx(&fasthttp.RequestCtx{})
defer app.ReleaseCtx(ctx)
ctx.Request().Header.SetMethod("POST")
ctx.Request().SetBody([]byte(fmt.Sprintf(`{"query": %q}`, tt.query)))
result := parseGraphQLQuery(ctx)
if result.shouldBlock != tt.wantBlocked {
t.Errorf("query blocked = %v, want %v", result.shouldBlock, tt.wantBlocked)
}
// Clean up test env vars
for k := range tt.envVars {
os.Unsetenv(k)
}
})
}
}
func TestMain(m *testing.M) {
// Setup test environment
os.Setenv("LOG_LEVEL", "error") // Reduce noise in tests
// Run tests
code := m.Run()
// Cleanup
os.Unsetenv("LOG_LEVEL")
os.Exit(code)
}