mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-01-17 16:34:25 +00:00
Fixed providers
This commit is contained in:
434
pkg/security/composite_test.go
Normal file
434
pkg/security/composite_test.go
Normal file
@@ -0,0 +1,434 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Mock implementations for testing composite provider
|
||||
type mockAuth struct {
|
||||
loginResp *LoginResponse
|
||||
loginErr error
|
||||
logoutErr error
|
||||
authUser *UserContext
|
||||
authErr error
|
||||
supportsRefresh bool
|
||||
supportsValidate bool
|
||||
}
|
||||
|
||||
func (m *mockAuth) Login(ctx context.Context, req LoginRequest) (*LoginResponse, error) {
|
||||
return m.loginResp, m.loginErr
|
||||
}
|
||||
|
||||
func (m *mockAuth) Logout(ctx context.Context, req LogoutRequest) error {
|
||||
return m.logoutErr
|
||||
}
|
||||
|
||||
func (m *mockAuth) Authenticate(r *http.Request) (*UserContext, error) {
|
||||
return m.authUser, m.authErr
|
||||
}
|
||||
|
||||
// Optional interface implementations
|
||||
func (m *mockAuth) RefreshToken(ctx context.Context, refreshToken string) (*LoginResponse, error) {
|
||||
if !m.supportsRefresh {
|
||||
return nil, errors.New("not supported")
|
||||
}
|
||||
return m.loginResp, m.loginErr
|
||||
}
|
||||
|
||||
func (m *mockAuth) ValidateToken(ctx context.Context, token string) (bool, error) {
|
||||
if !m.supportsValidate {
|
||||
return false, errors.New("not supported")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type mockColSec struct {
|
||||
rules []ColumnSecurity
|
||||
err error
|
||||
supportsCache bool
|
||||
}
|
||||
|
||||
func (m *mockColSec) GetColumnSecurity(ctx context.Context, userID int, schema, table string) ([]ColumnSecurity, error) {
|
||||
return m.rules, m.err
|
||||
}
|
||||
|
||||
func (m *mockColSec) ClearCache(ctx context.Context, userID int, schema, table string) error {
|
||||
if !m.supportsCache {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockRowSec struct {
|
||||
rowSec RowSecurity
|
||||
err error
|
||||
supportsCache bool
|
||||
}
|
||||
|
||||
func (m *mockRowSec) GetRowSecurity(ctx context.Context, userID int, schema, table string) (RowSecurity, error) {
|
||||
return m.rowSec, m.err
|
||||
}
|
||||
|
||||
func (m *mockRowSec) ClearCache(ctx context.Context, userID int, schema, table string) error {
|
||||
if !m.supportsCache {
|
||||
return errors.New("not supported")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Test NewCompositeSecurityProvider
|
||||
func TestNewCompositeSecurityProvider(t *testing.T) {
|
||||
t.Run("with all valid providers", func(t *testing.T) {
|
||||
auth := &mockAuth{}
|
||||
colSec := &mockColSec{}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
composite, err := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if composite == nil {
|
||||
t.Fatal("expected non-nil composite provider")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with nil authenticator", func(t *testing.T) {
|
||||
colSec := &mockColSec{}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
_, err := NewCompositeSecurityProvider(nil, colSec, rowSec)
|
||||
if err == nil {
|
||||
t.Fatal("expected error with nil authenticator")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with nil column security provider", func(t *testing.T) {
|
||||
auth := &mockAuth{}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
_, err := NewCompositeSecurityProvider(auth, nil, rowSec)
|
||||
if err == nil {
|
||||
t.Fatal("expected error with nil column security provider")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("with nil row security provider", func(t *testing.T) {
|
||||
auth := &mockAuth{}
|
||||
colSec := &mockColSec{}
|
||||
|
||||
_, err := NewCompositeSecurityProvider(auth, colSec, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected error with nil row security provider")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test CompositeSecurityProvider authentication delegation
|
||||
func TestCompositeSecurityProviderAuth(t *testing.T) {
|
||||
userCtx := &UserContext{
|
||||
UserID: 1,
|
||||
UserName: "testuser",
|
||||
}
|
||||
|
||||
t.Run("login delegates to authenticator", func(t *testing.T) {
|
||||
auth := &mockAuth{
|
||||
loginResp: &LoginResponse{
|
||||
Token: "abc123",
|
||||
User: userCtx,
|
||||
},
|
||||
}
|
||||
colSec := &mockColSec{}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
req := LoginRequest{Username: "test", Password: "pass"}
|
||||
|
||||
resp, err := composite.Login(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if resp.Token != "abc123" {
|
||||
t.Errorf("expected token abc123, got %s", resp.Token)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("logout delegates to authenticator", func(t *testing.T) {
|
||||
auth := &mockAuth{}
|
||||
colSec := &mockColSec{}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
req := LogoutRequest{Token: "abc123", UserID: 1}
|
||||
|
||||
err := composite.Logout(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("authenticate delegates to authenticator", func(t *testing.T) {
|
||||
auth := &mockAuth{
|
||||
authUser: userCtx,
|
||||
}
|
||||
colSec := &mockColSec{}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
|
||||
user, err := composite.Authenticate(req)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if user.UserID != 1 {
|
||||
t.Errorf("expected UserID 1, got %d", user.UserID)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test CompositeSecurityProvider security provider delegation
|
||||
func TestCompositeSecurityProviderSecurity(t *testing.T) {
|
||||
t.Run("get column security delegates to column provider", func(t *testing.T) {
|
||||
auth := &mockAuth{}
|
||||
colSec := &mockColSec{
|
||||
rules: []ColumnSecurity{
|
||||
{Schema: "public", Tablename: "users", Path: []string{"email"}},
|
||||
},
|
||||
}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
|
||||
rules, err := composite.GetColumnSecurity(ctx, 1, "public", "users")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if len(rules) != 1 {
|
||||
t.Errorf("expected 1 rule, got %d", len(rules))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("get row security delegates to row provider", func(t *testing.T) {
|
||||
auth := &mockAuth{}
|
||||
colSec := &mockColSec{}
|
||||
rowSec := &mockRowSec{
|
||||
rowSec: RowSecurity{
|
||||
Schema: "public",
|
||||
Tablename: "orders",
|
||||
Template: "user_id = {UserID}",
|
||||
},
|
||||
}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
|
||||
rowSecResult, err := composite.GetRowSecurity(ctx, 1, "public", "orders")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if rowSecResult.Template != "user_id = {UserID}" {
|
||||
t.Errorf("expected template 'user_id = {UserID}', got %s", rowSecResult.Template)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test CompositeSecurityProvider optional interfaces
|
||||
func TestCompositeSecurityProviderOptionalInterfaces(t *testing.T) {
|
||||
t.Run("refresh token with support", func(t *testing.T) {
|
||||
auth := &mockAuth{
|
||||
supportsRefresh: true,
|
||||
loginResp: &LoginResponse{
|
||||
Token: "new-token",
|
||||
},
|
||||
}
|
||||
colSec := &mockColSec{}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
|
||||
resp, err := composite.RefreshToken(ctx, "old-token")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if resp.Token != "new-token" {
|
||||
t.Errorf("expected token new-token, got %s", resp.Token)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("refresh token without support", func(t *testing.T) {
|
||||
auth := &mockAuth{
|
||||
supportsRefresh: false,
|
||||
}
|
||||
colSec := &mockColSec{}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := composite.RefreshToken(ctx, "token")
|
||||
if err == nil {
|
||||
t.Fatal("expected error when refresh not supported")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("validate token with support", func(t *testing.T) {
|
||||
auth := &mockAuth{
|
||||
supportsValidate: true,
|
||||
}
|
||||
colSec := &mockColSec{}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
|
||||
valid, err := composite.ValidateToken(ctx, "token")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if !valid {
|
||||
t.Error("expected token to be valid")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("validate token without support", func(t *testing.T) {
|
||||
auth := &mockAuth{
|
||||
supportsValidate: false,
|
||||
}
|
||||
colSec := &mockColSec{}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := composite.ValidateToken(ctx, "token")
|
||||
if err == nil {
|
||||
t.Fatal("expected error when validate not supported")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test CompositeSecurityProvider cache clearing
|
||||
func TestCompositeSecurityProviderClearCache(t *testing.T) {
|
||||
t.Run("clear cache with support", func(t *testing.T) {
|
||||
auth := &mockAuth{}
|
||||
colSec := &mockColSec{supportsCache: true}
|
||||
rowSec := &mockRowSec{supportsCache: true}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
|
||||
err := composite.ClearCache(ctx, 1, "public", "users")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("clear cache without support", func(t *testing.T) {
|
||||
auth := &mockAuth{}
|
||||
colSec := &mockColSec{supportsCache: false}
|
||||
rowSec := &mockRowSec{supportsCache: false}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
|
||||
// Should not error even if providers don't support cache
|
||||
// (they just won't implement the interface)
|
||||
err := composite.ClearCache(ctx, 1, "public", "users")
|
||||
if err != nil {
|
||||
// It's ok if this errors, as the providers don't implement Cacheable
|
||||
t.Logf("cache clear returned error as expected: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("clear cache with partial support", func(t *testing.T) {
|
||||
auth := &mockAuth{}
|
||||
colSec := &mockColSec{supportsCache: true}
|
||||
rowSec := &mockRowSec{supportsCache: false}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
|
||||
err := composite.ClearCache(ctx, 1, "public", "users")
|
||||
// Should succeed for column security even if row security fails
|
||||
if err == nil {
|
||||
t.Log("cache clear succeeded partially")
|
||||
} else {
|
||||
t.Logf("cache clear returned error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Test error propagation
|
||||
func TestCompositeSecurityProviderErrorPropagation(t *testing.T) {
|
||||
t.Run("login error propagates", func(t *testing.T) {
|
||||
auth := &mockAuth{
|
||||
loginErr: errors.New("invalid credentials"),
|
||||
}
|
||||
colSec := &mockColSec{}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := composite.Login(ctx, LoginRequest{})
|
||||
if err == nil {
|
||||
t.Fatal("expected error to propagate")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("authenticate error propagates", func(t *testing.T) {
|
||||
auth := &mockAuth{
|
||||
authErr: errors.New("invalid token"),
|
||||
}
|
||||
colSec := &mockColSec{}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
req := httptest.NewRequest("GET", "/test", nil)
|
||||
|
||||
_, err := composite.Authenticate(req)
|
||||
if err == nil {
|
||||
t.Fatal("expected error to propagate")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("column security error propagates", func(t *testing.T) {
|
||||
auth := &mockAuth{}
|
||||
colSec := &mockColSec{
|
||||
err: errors.New("failed to load column security"),
|
||||
}
|
||||
rowSec := &mockRowSec{}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := composite.GetColumnSecurity(ctx, 1, "public", "users")
|
||||
if err == nil {
|
||||
t.Fatal("expected error to propagate")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("row security error propagates", func(t *testing.T) {
|
||||
auth := &mockAuth{}
|
||||
colSec := &mockColSec{}
|
||||
rowSec := &mockRowSec{
|
||||
err: errors.New("failed to load row security"),
|
||||
}
|
||||
|
||||
composite, _ := NewCompositeSecurityProvider(auth, colSec, rowSec)
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := composite.GetRowSecurity(ctx, 1, "public", "orders")
|
||||
if err == nil {
|
||||
t.Fatal("expected error to propagate")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user