From 9be11d2cf7db43a579fd2a4fcd2ef7f5a2dc5d88 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Sat, 27 Jun 2026 19:00:12 +0200 Subject: [PATCH] fix(rest): reject malformed authorization URLs --- catalog/rest/rest.go | 14 ++++++++---- catalog/rest/rest_internal_test.go | 36 ++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/catalog/rest/rest.go b/catalog/rest/rest.go index ad6b7da4d..00f041f9d 100644 --- a/catalog/rest/rest.go +++ b/catalog/rest/rest.go @@ -448,7 +448,7 @@ func handleNon200(rsp *http.Response, override map[int]error) error { return e } -func fromProps(props iceberg.Properties, o *options) { +func fromProps(props iceberg.Properties, o *options) error { for k, v := range props { switch k { case keyWarehouseLocation: @@ -464,7 +464,7 @@ func fromProps(props iceberg.Properties, o *options) { case keyAuthUrl: u, err := url.Parse(v) if err != nil { - continue + return fmt.Errorf("invalid %s %q: %w", keyAuthUrl, v, err) } o.authUri = u case keyOauthCredential: @@ -496,6 +496,8 @@ func fromProps(props iceberg.Properties, o *options) { } } } + + return nil } func toProps(o *options) iceberg.Properties { @@ -551,7 +553,9 @@ type Catalog struct { func newCatalogFromProps(ctx context.Context, name string, uri string, p iceberg.Properties) (*Catalog, error) { var ops options - fromProps(p, &ops) + if err := fromProps(p, &ops); err != nil { + return nil, err + } r := &Catalog{name: name} if err := r.init(ctx, &ops, uri); err != nil { @@ -765,7 +769,9 @@ func (r *Catalog) fetchConfig(ctx context.Context, opts *options) (*http.Client, r.endpoints = resolveEndpoints(rsp.Endpoints, cfg.GetBool(keyViewEndpointsSupported, false)) o := *opts - fromProps(cfg, &o) + if err := fromProps(cfg, &o); err != nil { + return nil, nil, err + } if uri, ok := cfg["uri"]; ok { r.baseURI, err = url.Parse(uri) diff --git a/catalog/rest/rest_internal_test.go b/catalog/rest/rest_internal_test.go index 583cd769f..299f92eae 100644 --- a/catalog/rest/rest_internal_test.go +++ b/catalog/rest/rest_internal_test.go @@ -40,6 +40,8 @@ import ( "testing" "time" + "github.com/apache/iceberg-go" + "github.com/apache/iceberg-go/catalog" "github.com/aws/aws-sdk-go-v2/aws" v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" "github.com/aws/aws-sdk-go-v2/config" @@ -49,6 +51,40 @@ import ( "golang.org/x/sync/errgroup" ) +func TestLoadRegisteredCatalogRejectsInvalidAuthURL(t *testing.T) { + t.Parallel() + + cat, err := catalog.Load(context.Background(), "rest", iceberg.Properties{ + "uri": "http://example.com", + "rest.authorization-url": "http://[::1", + }) + require.Error(t, err) + assert.Nil(t, cat) + assert.ErrorContains(t, err, "invalid rest.authorization-url") +} + +func TestNewCatalogRejectsInvalidAuthURLFromConfig(t *testing.T) { + t.Parallel() + + mux := http.NewServeMux() + srv := httptest.NewServer(mux) + defer srv.Close() + + mux.HandleFunc("/v1/config", func(w http.ResponseWriter, r *http.Request) { + json.NewEncoder(w).Encode(map[string]any{ + "defaults": map[string]any{ + "rest.authorization-url": "http://[::1", + }, + "overrides": map[string]any{}, + }) + }) + + cat, err := NewCatalog(context.Background(), "rest", srv.URL) + require.Error(t, err) + assert.Nil(t, cat) + assert.ErrorContains(t, err, "invalid rest.authorization-url") +} + func TestTokenAuthenticationPriority(t *testing.T) { t.Parallel() mux := http.NewServeMux()