Skip to content

Commit 6c380b7

Browse files
committed
Migrate rubygems handler to OIDCRegistry
1 parent d088b52 commit 6c380b7

5 files changed

Lines changed: 33 additions & 231 deletions

File tree

internal/handlers/hex_organization.go

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,25 @@ import (
1313

1414
// HexOrganizationHandler handles requests to repo.hex.pm, adding auth.
1515
type HexOrganizationHandler struct {
16-
credentials []hexOrganizationCredentials
17-
}
18-
19-
type hexOrganizationCredentials struct {
20-
organization string
21-
key string
16+
orgTokens map[string]string
2217
}
2318

2419
// NewHexOrganizationHandler returns a new HexOrganizationHandler.
2520
func NewHexOrganizationHandler(creds config.Credentials) *HexOrganizationHandler {
26-
handler := HexOrganizationHandler{credentials: []hexOrganizationCredentials{}}
21+
handler := HexOrganizationHandler{orgTokens: map[string]string{}}
2722

2823
for _, cred := range creds {
2924
if cred["type"] != "hex_organization" {
3025
continue
3126
}
3227

3328
org := cred.GetString("organization")
34-
// Support both "key" and "token" (backwards compatibility)
35-
key := cred.GetString("key")
36-
if key == "" {
37-
key = cred.GetString("token")
38-
}
39-
if org == "" || key == "" {
29+
token := cred.GetString("token")
30+
if org == "" || token == "" {
4031
continue
4132
}
4233

43-
hexCred := hexOrganizationCredentials{
44-
organization: org,
45-
key: key,
46-
}
47-
handler.credentials = append(handler.credentials, hexCred)
34+
handler.orgTokens[org] = token
4835
}
4936

5037
return &handler
@@ -65,14 +52,13 @@ func (h *HexOrganizationHandler) HandleRequest(req *http.Request, ctx *goproxy.P
6552
return req, nil
6653
}
6754

68-
reqOrg := pathParts[1]
69-
for _, cred := range h.credentials {
70-
if cred.organization == reqOrg {
71-
logging.RequestLogf(ctx, "* authenticating hex request (org: %s)", reqOrg)
72-
req.Header.Set("authorization", cred.key)
73-
return req, nil
74-
}
55+
token, ok := h.orgTokens[pathParts[1]]
56+
if !ok {
57+
return req, nil
7558
}
7659

60+
logging.RequestLogf(ctx, "* authenticating hex request (org: %s)", pathParts[1])
61+
req.Header.Set("authorization", token)
62+
7763
return req, nil
7864
}

internal/handlers/hex_organization_test.go

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,29 @@ import (
88
)
99

1010
func TestHexOrganizationHandler(t *testing.T) {
11-
dependabotKey := "123"
12-
deltaForceKey := "456"
11+
dependabotToken := "123"
12+
deltaForceToken := "456"
1313
credentials := config.Credentials{
1414
config.Credential{
1515
"type": "hex_organization",
1616
"organization": "dependabot",
17-
"key": dependabotKey,
17+
"token": dependabotToken,
1818
},
1919
config.Credential{
2020
"type": "hex_organization",
2121
"organization": "deltaforce",
22-
"key": deltaForceKey,
22+
"token": deltaForceToken,
2323
},
2424
}
2525
handler := NewHexOrganizationHandler(credentials)
2626

2727
req := httptest.NewRequest("GET", "https://repo.hex.pm/repos/dependabot/packages/foo", nil)
2828
req = handleRequestAndClose(handler, req, nil)
29-
assertHasTokenAuth(t, req, "", dependabotKey, "dependabot registry request")
29+
assertHasTokenAuth(t, req, "", dependabotToken, "dependabot registry request")
3030

3131
req = httptest.NewRequest("GET", "https://repo.hex.pm/repos/deltaforce/packages/foo", nil)
3232
req = handleRequestAndClose(handler, req, nil)
33-
assertHasTokenAuth(t, req, "", deltaForceKey, "deltaforce registry request")
33+
assertHasTokenAuth(t, req, "", deltaForceToken, "deltaforce registry request")
3434

3535
// Not an org
3636
req = httptest.NewRequest("GET", "https://repo.hex.pm/packages/foo", nil)
@@ -52,36 +52,3 @@ func TestHexOrganizationHandler(t *testing.T) {
5252
req = handleRequestAndClose(handler, req, nil)
5353
assertUnauthenticated(t, req, "post request")
5454
}
55-
56-
func TestHexOrganizationHandler_BackwardsCompatibility(t *testing.T) {
57-
t.Run("supports legacy token field", func(t *testing.T) {
58-
credentials := config.Credentials{
59-
config.Credential{
60-
"type": "hex_organization",
61-
"organization": "legacy-org",
62-
"token": "legacy-token",
63-
},
64-
}
65-
handler := NewHexOrganizationHandler(credentials)
66-
67-
req := httptest.NewRequest("GET", "https://repo.hex.pm/repos/legacy-org/packages/foo", nil)
68-
req = handleRequestAndClose(handler, req, nil)
69-
assertHasTokenAuth(t, req, "", "legacy-token", "should support legacy token field")
70-
})
71-
72-
t.Run("key takes precedence over token", func(t *testing.T) {
73-
credentials := config.Credentials{
74-
config.Credential{
75-
"type": "hex_organization",
76-
"organization": "test-org",
77-
"key": "new-key",
78-
"token": "old-token",
79-
},
80-
}
81-
handler := NewHexOrganizationHandler(credentials)
82-
83-
req := httptest.NewRequest("GET", "https://repo.hex.pm/repos/test-org/packages/foo", nil)
84-
req = handleRequestAndClose(handler, req, nil)
85-
assertHasTokenAuth(t, req, "", "new-key", "key should take precedence over token")
86-
})
87-
}

internal/handlers/rubygems_server.go

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package handlers
33
import (
44
"net/http"
55
"strings"
6-
"sync"
76

87
"github.com/elazarl/goproxy"
98

@@ -15,9 +14,8 @@ import (
1514

1615
// RubyGemsServerHandler handles requests to rubygems servers, adding auth.
1716
type RubyGemsServerHandler struct {
18-
credentials []rubyGemsServerCredentials
19-
oidcCredentials map[string]*oidc.OIDCCredential
20-
mutex sync.RWMutex
17+
credentials []rubyGemsServerCredentials
18+
oidcRegistry *oidc.OIDCRegistry
2119
}
2220

2321
type rubyGemsServerCredentials struct {
@@ -29,8 +27,8 @@ type rubyGemsServerCredentials struct {
2927
// NewRubyGemsServerHandler returns a new RubyGemsServerHandler.
3028
func NewRubyGemsServerHandler(creds config.Credentials) *RubyGemsServerHandler {
3129
handler := RubyGemsServerHandler{
32-
credentials: []rubyGemsServerCredentials{},
33-
oidcCredentials: make(map[string]*oidc.OIDCCredential),
30+
credentials: []rubyGemsServerCredentials{},
31+
oidcRegistry: oidc.NewOIDCRegistry(),
3432
}
3533

3634
for _, cred := range creds {
@@ -41,16 +39,8 @@ func NewRubyGemsServerHandler(creds config.Credentials) *RubyGemsServerHandler {
4139
host := cred.Host()
4240
url := cred.GetString("url")
4341

44-
oidcCredential, _ := oidc.CreateOIDCCredential(cred)
45-
if oidcCredential != nil {
46-
hostURL := url
47-
if hostURL == "" {
48-
hostURL = host
49-
}
50-
if hostURL != "" {
51-
handler.oidcCredentials[hostURL] = oidcCredential
52-
logging.RequestLogf(nil, "registered %s OIDC credentials for rubygems server: %s", oidcCredential.Provider(), hostURL)
53-
}
42+
// OIDC credentials are not used as static credentials.
43+
if oidcCred, _, _ := handler.oidcRegistry.Register(cred, []string{"url"}, "rubygems server"); oidcCred != nil {
5444
continue
5545
}
5646

@@ -72,7 +62,7 @@ func (h *RubyGemsServerHandler) HandleRequest(req *http.Request, ctx *goproxy.Pr
7262
}
7363

7464
// Try OIDC credentials first
75-
if oidc.TryAuthOIDCRequestWithPrefix(&h.mutex, h.oidcCredentials, req, ctx) {
65+
if h.oidcRegistry.TryAuth(req, ctx) {
7666
return req, nil
7767
}
7868

internal/handlers/terraform_registry.go

Lines changed: 9 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package handlers
22

33
import (
44
"net/http"
5-
"sort"
6-
"strings"
75
"sync"
86

97
"github.com/elazarl/goproxy"
@@ -15,20 +13,14 @@ import (
1513
)
1614

1715
type TerraformRegistryHandler struct {
18-
credentials []terraformRegistryCredentials
16+
credentials map[string]string
1917
oidcCredentials map[string]*oidc.OIDCCredential
2018
mutex sync.RWMutex
2119
}
2220

23-
type terraformRegistryCredentials struct {
24-
host string
25-
url string
26-
token string
27-
}
28-
2921
func NewTerraformRegistryHandler(credentials config.Credentials) *TerraformRegistryHandler {
3022
handler := TerraformRegistryHandler{
31-
credentials: []terraformRegistryCredentials{},
23+
credentials: make(map[string]string),
3224
oidcCredentials: make(map[string]*oidc.OIDCCredential),
3325
}
3426

@@ -48,33 +40,8 @@ func NewTerraformRegistryHandler(credentials config.Credentials) *TerraformRegis
4840
continue
4941
}
5042

51-
token := credential.GetString("token")
52-
url := credential.GetString("url")
53-
54-
// Skip credentials with empty token or both empty host and url
55-
if token == "" || (host == "" && url == "") {
56-
continue
57-
}
58-
59-
terraformCred := terraformRegistryCredentials{
60-
url: url,
61-
token: token,
62-
}
63-
// Only set host when url is not provided to ensure URL-prefix matching
64-
// takes precedence and doesn't fall back to host matching
65-
if url == "" {
66-
terraformCred.host = host
67-
}
68-
handler.credentials = append(handler.credentials, terraformCred)
43+
handler.credentials[host] = credential.GetString("token")
6944
}
70-
71-
// Sort credentials by URL length descending (longest first) to ensure
72-
// more specific URLs match before shorter ones. Using SliceStable for
73-
// deterministic ordering when URL lengths are equal.
74-
sort.SliceStable(handler.credentials, func(i, j int) bool {
75-
return len(handler.credentials[i].url) > len(handler.credentials[j].url)
76-
})
77-
7845
return &handler
7946
}
8047

@@ -89,65 +56,15 @@ func (h *TerraformRegistryHandler) HandleRequest(request *http.Request, context
8956
}
9057

9158
// Fall back to static credentials
92-
for _, cred := range h.credentials {
93-
if !urlMatchesRequestWithBoundary(request, cred.url) && !helpers.CheckHost(request, cred.host) {
94-
continue
95-
}
59+
host := request.URL.Hostname()
60+
token, ok := h.credentials[host]
9661

97-
logging.RequestLogf(context, "* authenticating terraform registry request (host: %s)", request.URL.Hostname())
98-
request.Header.Set("Authorization", "Bearer "+cred.token)
62+
if !ok {
9963
return request, nil
10064
}
10165

102-
return request, nil
103-
}
104-
105-
// urlMatchesRequestWithBoundary checks if the request URL matches the credential URL
106-
// with proper path boundary checking.
107-
func urlMatchesRequestWithBoundary(request *http.Request, credURL string) bool {
108-
if credURL == "" {
109-
return false
110-
}
111-
112-
parsedURL, err := helpers.ParseURLLax(credURL)
113-
if err != nil {
114-
return false
115-
}
116-
117-
if !helpers.AreHostnamesEqual(parsedURL.Hostname(), request.URL.Hostname()) {
118-
return false
119-
}
120-
121-
urlPort := parsedURL.Port()
122-
if urlPort == "" {
123-
urlPort = "443"
124-
}
125-
126-
reqPort := request.URL.Port()
127-
if reqPort == "" {
128-
reqPort = "443"
129-
}
130-
131-
if urlPort != reqPort {
132-
return false
133-
}
66+
logging.RequestLogf(context, "* authenticating terraform registry request (host: %s)", host)
67+
request.Header.Set("Authorization", "Bearer "+token)
13468

135-
credPath := strings.TrimRight(parsedURL.Path, "/")
136-
reqPath := request.URL.Path
137-
138-
if credPath == "" {
139-
// Empty path matches everything on the host
140-
return true
141-
}
142-
143-
if reqPath == credPath {
144-
return true
145-
}
146-
147-
// Check if request path starts with credPath followed by /
148-
if strings.HasPrefix(reqPath, credPath+"/") {
149-
return true
150-
}
151-
152-
return false
69+
return request, nil
15370
}

internal/handlers/terraform_registry_test.go

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -74,62 +74,4 @@ func TestTerraformRegistryHandler(t *testing.T) {
7474

7575
assert.Equal(t, "", request.Header.Get("Authorization"), "should be empty")
7676
})
77-
78-
t.Run("multiple credentials on same host with different URL paths", func(t *testing.T) {
79-
credentials := config.Credentials{
80-
config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org1", "token": "token-org1"},
81-
config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org2", "token": "token-org2"},
82-
}
83-
handler := NewTerraformRegistryHandler(credentials)
84-
85-
// Request to org1 path should use org1 token
86-
req1 := handleRequestAndClose(handler, httptest.NewRequest("GET", "https://terraform.example.com/org1/v1/providers/foo", nil), nil)
87-
assert.Equal(t, "Bearer token-org1", req1.Header.Get("Authorization"), "should use org1 token")
88-
89-
// Request to org2 path should use org2 token
90-
req2 := handleRequestAndClose(handler, httptest.NewRequest("GET", "https://terraform.example.com/org2/v1/providers/bar", nil), nil)
91-
assert.Equal(t, "Bearer token-org2", req2.Header.Get("Authorization"), "should use org2 token")
92-
93-
// Request to unmatched path should not be authenticated
94-
req3 := handleRequestAndClose(handler, httptest.NewRequest("GET", "https://terraform.example.com/org3/v1/providers/baz", nil), nil)
95-
assert.Equal(t, "", req3.Header.Get("Authorization"), "should not be authenticated")
96-
})
97-
98-
t.Run("skips credentials with empty token", func(t *testing.T) {
99-
credentials := config.Credentials{
100-
config.Credential{"type": "terraform_registry", "host": "terraform.example.org", "token": ""},
101-
}
102-
handler := NewTerraformRegistryHandler(credentials)
103-
assert.Equal(t, 0, len(handler.credentials), "should skip credential with empty token")
104-
})
105-
106-
t.Run("skips credentials with empty host and url", func(t *testing.T) {
107-
credentials := config.Credentials{
108-
config.Credential{"type": "terraform_registry", "token": "some-token"},
109-
}
110-
handler := NewTerraformRegistryHandler(credentials)
111-
assert.Equal(t, 0, len(handler.credentials), "should skip credential with empty host and url")
112-
})
113-
114-
t.Run("path boundary: /org should not match /org1", func(t *testing.T) {
115-
// Credentials are sorted longest-path-first to ensure /org1 matches before /org
116-
credentials := config.Credentials{
117-
config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org", "token": "token-org"},
118-
config.Credential{"type": "terraform_registry", "url": "https://terraform.example.com/org1", "token": "token-org1"},
119-
}
120-
handler := NewTerraformRegistryHandler(credentials)
121-
122-
assert.Equal(t, "https://terraform.example.com/org1", handler.credentials[0].url, "longer path should be first")
123-
assert.Equal(t, "https://terraform.example.com/org", handler.credentials[1].url, "shorter path should be second")
124-
125-
req1 := handleRequestAndClose(handler, httptest.NewRequest("GET", "https://terraform.example.com/org1/v1/providers/foo", nil), nil)
126-
assert.Equal(t, "Bearer token-org1", req1.Header.Get("Authorization"), "/org1 path should use org1 token")
127-
128-
req2 := handleRequestAndClose(handler, httptest.NewRequest("GET", "https://terraform.example.com/org/v1/providers/bar", nil), nil)
129-
assert.Equal(t, "Bearer token-org", req2.Header.Get("Authorization"), "/org path should use org token")
130-
131-
// Request to /org123 should NOT match /org1 or /org (path boundary check)
132-
req3 := handleRequestAndClose(handler, httptest.NewRequest("GET", "https://terraform.example.com/org123/v1/providers/baz", nil), nil)
133-
assert.Equal(t, "", req3.Header.Get("Authorization"), "/org123 should not match /org or /org1")
134-
})
13577
}

0 commit comments

Comments
 (0)