Skip to content

Commit 410ba07

Browse files
fix(oidc): Validate HTTP response status and improve error messaging in OIDC (#308) (#316)
1 parent f23acf9 commit 410ba07

File tree

2 files changed

+181
-4
lines changed

2 files changed

+181
-4
lines changed

internal/oidc/oidc.go

+11-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"io"
78
"net/http"
89
"net/url"
910
"path"
@@ -24,18 +25,24 @@ func GetWellKnownEndpointsFromIssuerURL(
2425

2526
request, err := http.NewRequestWithContext(ctx, http.MethodGet, issuerURL.String(), nil)
2627
if err != nil {
27-
return nil, fmt.Errorf("could not build request to get well known endpoints: %w", err)
28+
return nil, fmt.Errorf("could not build request to get well-known endpoints: %w", err)
2829
}
2930

3031
response, err := httpClient.Do(request)
3132
if err != nil {
32-
return nil, fmt.Errorf("could not get well known endpoints from url %s: %w", issuerURL.String(), err)
33+
return nil, fmt.Errorf("could not fetch well-known endpoints from %s: %w", issuerURL.String(), err)
3334
}
3435
defer response.Body.Close()
3536

37+
if response.StatusCode < 200 || response.StatusCode >= 300 {
38+
body, _ := io.ReadAll(response.Body)
39+
return nil, fmt.Errorf("received HTTP %d from %s: %s",
40+
response.StatusCode, issuerURL.String(), string(body))
41+
}
42+
3643
var wkEndpoints WellKnownEndpoints
37-
if err = json.NewDecoder(response.Body).Decode(&wkEndpoints); err != nil {
38-
return nil, fmt.Errorf("could not decode json body when getting well known endpoints: %w", err)
44+
if err := json.NewDecoder(response.Body).Decode(&wkEndpoints); err != nil {
45+
return nil, fmt.Errorf("failed to decode JSON response from %s: %w", issuerURL.String(), err)
3946
}
4047

4148
return &wkEndpoints, nil

internal/oidc/oidc_test.go

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package oidc
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"net/url"
8+
"strings"
9+
"testing"
10+
"time"
11+
)
12+
13+
// setupTestServer creates a test HTTP server that returns the specified response code and body.
14+
func setupTestServer(responseCode int, responseBody string, headers map[string]string) *httptest.Server {
15+
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
16+
for key, value := range headers {
17+
w.Header().Set(key, value)
18+
}
19+
w.WriteHeader(responseCode)
20+
_, _ = w.Write([]byte(responseBody))
21+
}))
22+
}
23+
24+
// TestGetWellKnownEndpointsFromIssuerURL tests various scenarios for GetWellKnownEndpointsFromIssuerURL.
25+
func TestGetWellKnownEndpointsFromIssuerURL(t *testing.T) {
26+
tests := []struct {
27+
name string
28+
responseCode int
29+
responseBody string
30+
headers map[string]string
31+
expectError bool
32+
}{
33+
{
34+
name: "Successful 200 response with valid JSON",
35+
responseCode: http.StatusOK,
36+
responseBody: `{"jwks_uri":"https://example.com/jwks"}`,
37+
headers: map[string]string{"Content-Type": "application/json"},
38+
expectError: false,
39+
},
40+
{
41+
name: "404 Not Found response",
42+
responseCode: http.StatusNotFound,
43+
responseBody: `{"error": "not found"}`,
44+
expectError: true,
45+
},
46+
{
47+
name: "500 Internal Server Error response",
48+
responseCode: http.StatusInternalServerError,
49+
responseBody: `Internal Server Error`,
50+
expectError: true,
51+
},
52+
{
53+
name: "Malformed JSON response",
54+
responseCode: http.StatusOK,
55+
responseBody: `{"jwks_uri": "https://example.com/jwks"`, // Missing closing brace
56+
expectError: true,
57+
},
58+
{
59+
name: "Empty response",
60+
responseCode: http.StatusOK,
61+
responseBody: ``,
62+
expectError: true,
63+
},
64+
{
65+
name: "Non-JSON response",
66+
responseCode: http.StatusOK,
67+
responseBody: `<html><body>Error</body></html>`,
68+
headers: map[string]string{"Content-Type": "text/html"},
69+
expectError: true,
70+
},
71+
{
72+
name: "Redirect response",
73+
responseCode: http.StatusFound,
74+
responseBody: "",
75+
expectError: true,
76+
},
77+
{
78+
name: "Unauthorized response",
79+
responseCode: http.StatusUnauthorized,
80+
responseBody: `{"error": "unauthorized"}`,
81+
expectError: true,
82+
},
83+
{
84+
name: "Forbidden response",
85+
responseCode: http.StatusForbidden,
86+
responseBody: `{"error": "forbidden"}`,
87+
expectError: true,
88+
},
89+
}
90+
91+
for _, tt := range tests {
92+
t.Run(tt.name, func(t *testing.T) {
93+
server := setupTestServer(tt.responseCode, tt.responseBody, tt.headers)
94+
defer server.Close()
95+
96+
issuerURL, _ := url.Parse(server.URL)
97+
ctx := context.Background()
98+
client := &http.Client{}
99+
_, err := GetWellKnownEndpointsFromIssuerURL(ctx, client, *issuerURL)
100+
101+
if tt.expectError {
102+
if err == nil {
103+
t.Errorf("Expected error but got none")
104+
}
105+
} else {
106+
if err != nil {
107+
t.Errorf("Unexpected error: %v", err)
108+
}
109+
}
110+
})
111+
}
112+
}
113+
114+
// Simulate a network failure scenario
115+
func TestGetWellKnownEndpoints_NetworkError(t *testing.T) {
116+
client := &http.Client{}
117+
invalidURL, _ := url.Parse("http://invalid.local")
118+
_, err := GetWellKnownEndpointsFromIssuerURL(context.Background(), client, *invalidURL)
119+
120+
if err == nil || !strings.Contains(err.Error(), "could not fetch well-known endpoints") {
121+
t.Errorf("Unexpected error: %v", err)
122+
}
123+
}
124+
125+
// Simulate a timeout scenario
126+
func TestGetWellKnownEndpoints_Timeout(t *testing.T) {
127+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
128+
time.Sleep(2 * time.Second)
129+
}))
130+
defer server.Close()
131+
132+
client := &http.Client{Timeout: 1 * time.Second}
133+
issuerURL, _ := url.Parse(server.URL)
134+
_, err := GetWellKnownEndpointsFromIssuerURL(context.Background(), client, *issuerURL)
135+
136+
if err == nil || !strings.Contains(err.Error(), "context deadline exceeded") {
137+
t.Errorf("Expected timeout error, got: %v", err)
138+
}
139+
}
140+
141+
// Test invalid request creation
142+
func TestGetWellKnownEndpoints_InvalidRequest(t *testing.T) {
143+
client := &http.Client{}
144+
145+
invalidURL := url.URL{Scheme: ":", Host: ""}
146+
147+
_, err := GetWellKnownEndpointsFromIssuerURL(context.Background(), client, invalidURL)
148+
149+
if err == nil || !strings.Contains(err.Error(), "could not build request to get well-known endpoints") {
150+
t.Errorf("Expected request creation error, got: %v", err)
151+
}
152+
}
153+
154+
// Test response body read failure
155+
func TestGetWellKnownEndpoints_BodyReadFailure(t *testing.T) {
156+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
157+
w.WriteHeader(http.StatusOK)
158+
w.(http.Flusher).Flush()
159+
}))
160+
server.CloseClientConnections()
161+
defer server.Close()
162+
163+
client := &http.Client{}
164+
issuerURL, _ := url.Parse(server.URL)
165+
_, err := GetWellKnownEndpointsFromIssuerURL(context.Background(), client, *issuerURL)
166+
167+
if err == nil || !strings.Contains(err.Error(), "failed to decode JSON") {
168+
t.Errorf("Expected body read failure error, got: %v", err)
169+
}
170+
}

0 commit comments

Comments
 (0)