Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions internal/outpost/proxyv2/application/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ func (a *Application) getClaimsFromSession(r *http.Request) *types.Claims {

// Claims are always stored as types.Claims but may be deserialized differently:
// - Filesystem store (gob): preserves struct type as types.Claims
// - PostgreSQL store (JSON): deserializes as map[string]interface{}
// - PostgreSQL store (JSON): deserializes as map[string]any

// Handle struct type (filesystem store)
if c, ok := claims.(types.Claims); ok {
return &c
}

// Handle map type (PostgreSQL store)
if claimsMap, ok := claims.(map[string]interface{}); ok {
if claimsMap, ok := claims.(map[string]any); ok {
var c types.Claims
if err := mapstructure.Decode(claimsMap, &c); err != nil {
return nil
Expand Down
50 changes: 25 additions & 25 deletions internal/outpost/proxyv2/application/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"

"github.com/gorilla/sessions"
"github.com/mitchellh/mapstructure"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand All @@ -27,7 +28,7 @@ func TestClaimsJSONSerialization(t *testing.T) {
Entitlements: []string{"read", "write"},
Sid: "session-id-456",
Proxy: &types.ProxyClaims{
UserAttributes: map[string]interface{}{
UserAttributes: map[string]any{
"custom_field": "custom_value",
"department": "engineering",
},
Expand Down Expand Up @@ -70,35 +71,33 @@ func TestClaimsJSONSerialization(t *testing.T) {
assert.Equal(t, "engineering", parsedClaims.Proxy.UserAttributes["department"])
}

// TestClaimsMapSerialization tests that Claims stored as map[string]interface{} can be converted back
// TestClaimsMapSerialization tests that Claims stored as map[string]any can be converted back
func TestClaimsMapSerialization(t *testing.T) {
// Simulate how claims are stored in session as map (like from PostgreSQL JSONB)
claimsMap := map[string]interface{}{
claimsMap := map[string]any{
"sub": "user-id-123",
"exp": float64(1234567890), // json numbers become float64
"email": "test@example.com",
"email_verified": true,
"name": "Test User",
"preferred_username": "testuser",
"groups": []interface{}{"admin", "user"},
"entitlements": []interface{}{"read", "write"},
"groups": []any{"admin", "user"},
"entitlements": []any{"read", "write"},
"sid": "session-id-456",
"ak_proxy": map[string]interface{}{
"user_attributes": map[string]interface{}{
"ak_proxy": map[string]any{
"user_attributes": map[string]any{
"custom_field": "custom_value",
},
"backend_override": "custom-backend",
"host_header": "example.com",
"is_superuser": true,
},
"raw_token": "not-a-real-token",
}

// Convert map to Claims using JSON marshaling (like getClaimsFromSession does)
jsonData, err := json.Marshal(claimsMap)
require.NoError(t, err)

// Convert map to Claims using mapstructure marshaling (like getClaimsFromSession does)
var claims types.Claims
err = json.Unmarshal(jsonData, &claims)
err := mapstructure.Decode(claimsMap, &claims)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more mapstructure, good catch

require.NoError(t, err)

// Verify fields
Expand All @@ -111,6 +110,7 @@ func TestClaimsMapSerialization(t *testing.T) {
assert.Equal(t, []string{"admin", "user"}, claims.Groups)
assert.Equal(t, []string{"read", "write"}, claims.Entitlements)
assert.Equal(t, "session-id-456", claims.Sid)
assert.Equal(t, "not-a-real-token", claims.RawToken)

// Verify proxy claims
require.NotNil(t, claims.Proxy)
Expand All @@ -122,7 +122,7 @@ func TestClaimsMapSerialization(t *testing.T) {

// TestClaimsMinimalFields tests that Claims work with minimal required fields
func TestClaimsMinimalFields(t *testing.T) {
claimsMap := map[string]interface{}{
claimsMap := map[string]any{
"sub": "user-id-123",
"exp": float64(1234567890),
}
Expand All @@ -144,11 +144,11 @@ func TestClaimsMinimalFields(t *testing.T) {

// TestClaimsWithEmptyArrays tests that empty arrays are handled correctly
func TestClaimsWithEmptyArrays(t *testing.T) {
claimsMap := map[string]interface{}{
claimsMap := map[string]any{
"sub": "user-id-123",
"exp": float64(1234567890),
"groups": []interface{}{},
"entitlements": []interface{}{},
"groups": []any{},
"entitlements": []any{},
}

jsonData, err := json.Marshal(claimsMap)
Expand All @@ -167,7 +167,7 @@ func TestClaimsWithEmptyArrays(t *testing.T) {

// TestClaimsWithNullProxyClaims tests that null proxy claims don't cause issues
func TestClaimsWithNullProxyClaims(t *testing.T) {
claimsMap := map[string]interface{}{
claimsMap := map[string]any{
"sub": "user-id-123",
"exp": float64(1234567890),
"ak_proxy": nil,
Expand All @@ -185,18 +185,18 @@ func TestClaimsWithNullProxyClaims(t *testing.T) {
}

// TestGetClaimsFromSession_Success tests successful retrieval of claims from session
// uses a mock session that returns claims as map[string]interface{} to simulate
// uses a mock session that returns claims as map[string]any to simulate
// how PostgreSQL storage deserializes JSONB data
func TestGetClaimsFromSession_Success(t *testing.T) {
// Create a custom mock store that returns claims as map
store := &mockMapSessionStore{
claimsMap: map[string]interface{}{
claimsMap: map[string]any{
"sub": "user-id-123",
"exp": float64(1234567890),
"email": "test@example.com",
"email_verified": true,
"preferred_username": "testuser",
"groups": []interface{}{"admin", "user"},
"groups": []any{"admin", "user"},
},
}

Expand All @@ -217,9 +217,9 @@ func TestGetClaimsFromSession_Success(t *testing.T) {
assert.Equal(t, []string{"admin", "user"}, claims.Groups)
}

// mockMapSessionStore is a mock session store that returns claims as map[string]interface{}
// mockMapSessionStore is a mock session store that returns claims as map[string]any
type mockMapSessionStore struct {
claimsMap map[string]interface{}
claimsMap map[string]any
}

func (m *mockMapSessionStore) Get(r *http.Request, name string) (*sessions.Session, error) {
Expand Down Expand Up @@ -314,7 +314,7 @@ func TestClaimsRoundTrip(t *testing.T) {
Entitlements: []string{"ent1", "ent2"},
Sid: "session-789",
Proxy: &types.ProxyClaims{
UserAttributes: map[string]interface{}{
UserAttributes: map[string]any{
"attr1": "value1",
"attr2": float64(42),
"attr3": true,
Expand All @@ -329,8 +329,8 @@ func TestClaimsRoundTrip(t *testing.T) {
jsonData, err := json.Marshal(originalClaims)
require.NoError(t, err)

// Step 2: Deserialize to map[string]interface{} (simulating PostgreSQL load)
var claimsMap map[string]interface{}
// Step 2: Deserialize to map[string]any (simulating PostgreSQL load)
var claimsMap map[string]any
err = json.Unmarshal(jsonData, &claimsMap)
require.NoError(t, err)

Expand Down
10 changes: 5 additions & 5 deletions internal/outpost/proxyv2/types/claims.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package types

type ProxyClaims struct {
UserAttributes map[string]interface{} `json:"user_attributes"`
BackendOverride string `json:"backend_override"`
HostHeader string `json:"host_header"`
IsSuperuser bool `json:"is_superuser"`
UserAttributes map[string]any `json:"user_attributes" mapstructure:"user_attributes"`
BackendOverride string `json:"backend_override" mapstructure:"backend_override"`
HostHeader string `json:"host_header" mapstructure:"host_header"`
IsSuperuser bool `json:"is_superuser" mapstructure:"is_superuser"`
}

type Claims struct {
Expand All @@ -19,5 +19,5 @@ type Claims struct {
Sid string `json:"sid" mapstructure:"sid"`
Proxy *ProxyClaims `json:"ak_proxy" mapstructure:"ak_proxy"`

RawToken string `mapstructure:"-"`
RawToken string `json:"raw_token" mapstructure:"raw_token"`
}
Loading