rdev/internal/auth/service_ip_test.go
jordan bc47e426b0 feat: Add CI pipeline proxy, DNS alias management, and worker executor system
- Add ListPipelines/GetPipeline to CIProvider port with Woodpecker adapter
- Add DNS alias endpoints: GET/POST/DELETE /projects/{id}/domains
- Implement worker executor daemon, build executor, and git operations
- Add build service, worker service, and build audit tracking
- Add worker registry with PostgreSQL adapter and migration
- Add multi-provider code agent interface (Claude Code + OpenCode)
- Add create-and-build combo endpoint
- Update landing-page cookbook to reflect all gaps closed
- Fix tech debt: unified validation, auth scopes, error wrapping, slog patterns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 21:05:28 -07:00

191 lines
4.8 KiB
Go

package auth
import (
"context"
"testing"
"time"
)
func TestAPIKey_IsIPAllowed(t *testing.T) {
tests := []struct {
name string
allowedIPs []string
clientIP string
want bool
}{
{
name: "no restrictions - any IP allowed",
allowedIPs: nil,
clientIP: "192.168.1.100",
want: true,
},
{
name: "empty restrictions - any IP allowed",
allowedIPs: []string{},
clientIP: "10.0.0.5",
want: true,
},
{
name: "single IP match",
allowedIPs: []string{"192.168.1.100"},
clientIP: "192.168.1.100",
want: true,
},
{
name: "single IP no match",
allowedIPs: []string{"192.168.1.100"},
clientIP: "192.168.1.101",
want: false,
},
{
name: "CIDR match",
allowedIPs: []string{"192.168.1.0/24"},
clientIP: "192.168.1.55",
want: true,
},
{
name: "CIDR no match",
allowedIPs: []string{"192.168.1.0/24"},
clientIP: "192.168.2.1",
want: false,
},
{
name: "multiple CIDRs - first matches",
allowedIPs: []string{"10.0.0.0/8", "192.168.0.0/16"},
clientIP: "10.50.25.100",
want: true,
},
{
name: "multiple CIDRs - second matches",
allowedIPs: []string{"10.0.0.0/8", "192.168.0.0/16"},
clientIP: "192.168.50.1",
want: true,
},
{
name: "multiple CIDRs - none match",
allowedIPs: []string{"10.0.0.0/8", "192.168.0.0/16"},
clientIP: "172.16.0.1",
want: false,
},
{
name: "mixed IP and CIDR - IP matches",
allowedIPs: []string{"10.0.0.0/8", "172.16.0.1"},
clientIP: "172.16.0.1",
want: true,
},
{
name: "mixed IP and CIDR - CIDR matches",
allowedIPs: []string{"10.0.0.0/8", "172.16.0.1"},
clientIP: "10.1.2.3",
want: true,
},
{
name: "IPv6 CIDR",
allowedIPs: []string{"2001:db8::/32"},
clientIP: "2001:db8:1234:5678::1",
want: true,
},
{
name: "IPv6 no match",
allowedIPs: []string{"2001:db8::/32"},
clientIP: "2001:db9::1",
want: false,
},
{
name: "invalid client IP",
allowedIPs: []string{"192.168.1.0/24"},
clientIP: "not-an-ip",
want: false,
},
{
name: "invalid CIDR in allowlist (fallback to IP parse)",
allowedIPs: []string{"invalid/cidr", "192.168.1.100"},
clientIP: "192.168.1.100",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
key := &APIKey{AllowedIPs: tt.allowedIPs}
if got := key.IsIPAllowed(tt.clientIP); got != tt.want {
t.Errorf("IsIPAllowed(%q) = %v, want %v", tt.clientIP, got, tt.want)
}
})
}
}
func TestService_CreateWithAllowedIPs(t *testing.T) {
svc := newTestService(t, "admin-key")
t.Run("creates key with IP restrictions", func(t *testing.T) {
resp, err := svc.Create(context.Background(), CreateKeyRequest{
Name: "test-ip-key",
Scopes: []Scope{ScopeProjectsRead},
AllowedIPs: []string{"192.168.1.0/24", "10.0.0.1"},
ExpiresIn: 24 * time.Hour,
CreatedBy: "test",
})
if err != nil {
t.Fatalf("Create() error = %v", err)
}
if len(resp.Key.AllowedIPs) != 2 {
t.Errorf("Key.AllowedIPs length = %d, want 2", len(resp.Key.AllowedIPs))
}
key, err := svc.Get(context.Background(), string(resp.Key.ID))
if err != nil {
t.Fatalf("Get() error = %v", err)
}
if len(key.AllowedIPs) != 2 {
t.Errorf("Retrieved Key.AllowedIPs length = %d, want 2", len(key.AllowedIPs))
}
validatedKey, err := svc.Validate(context.Background(), resp.Secret)
if err != nil {
t.Fatalf("Validate() error = %v", err)
}
if len(validatedKey.AllowedIPs) != 2 {
t.Errorf("Validated Key.AllowedIPs length = %d, want 2", len(validatedKey.AllowedIPs))
}
if !validatedKey.IsIPAllowed("192.168.1.50") {
t.Error("IsIPAllowed should return true for IP in allowed CIDR")
}
if !validatedKey.IsIPAllowed("10.0.0.1") {
t.Error("IsIPAllowed should return true for explicitly allowed IP")
}
if validatedKey.IsIPAllowed("172.16.0.1") {
t.Error("IsIPAllowed should return false for IP not in allowed list")
}
})
t.Run("creates key with no IP restrictions", func(t *testing.T) {
resp, err := svc.Create(context.Background(), CreateKeyRequest{
Name: "test-no-ip-key",
Scopes: []Scope{ScopeProjectsRead},
ExpiresIn: 24 * time.Hour,
CreatedBy: "test",
})
if err != nil {
t.Fatalf("Create() error = %v", err)
}
if len(resp.Key.AllowedIPs) != 0 {
t.Errorf("Key.AllowedIPs should be empty, got %v", resp.Key.AllowedIPs)
}
validatedKey, err := svc.Validate(context.Background(), resp.Secret)
if err != nil {
t.Fatalf("Validate() error = %v", err)
}
if !validatedKey.IsIPAllowed("1.2.3.4") {
t.Error("IsIPAllowed should return true when no restrictions set")
}
})
}