laskoviymishka commented on code in PR #1336:
URL: https://github.com/apache/iceberg-go/pull/1336#discussion_r3491566501


##########
catalog/rest/rest.go:
##########
@@ -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)

Review Comment:
   One subtlety with switching `continue` to `return` here: we iterate `props` 
as a Go map, so the order is non-deterministic, and returning on the first bad 
value abandons every key we haven't visited yet.
   
   In practice the only failure here is a bad auth URL so the abandoned-keys 
part is mostly theoretical, but the non-determinism is real — if a future 
change adds a second validating case, which error surfaces will vary run to 
run. I'd lean toward validating-then-applying (or collecting the first error 
and returning it after the loop) so the behavior is stable. Not blocking on its 
own, but cheap to make deterministic while we're here.



##########
catalog/rest/rest.go:
##########
@@ -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)

Review Comment:
   I think this only catches part of the problem we're trying to fix. 
`url.Parse` is permissive enough that it only errors on byte-level malformation 
— the `http://[::1` fixture, broken percent-encoding, that class. The common 
typo is a missing scheme, and those slip through.
   
   `url.Parse("localhost:8080")`, `url.Parse("example.com/auth")`, and 
`url.Parse("")` all succeed and hand back a URL with an empty `Scheme`/`Host`. 
That sets `o.authUri` non-nil, so it bypasses the `authUri != nil` fallback 
later and the OAuth exchange targets the wrong endpoint — the exact silent 
misdirection this PR is closing, just via the typo people are actually likely 
to make.
   
   I'd validate scheme and host right here:
   
   ```go
   if u.Scheme == "" || u.Host == "" {
       return fmt.Errorf("invalid %s %q: missing scheme or host", keyAuthUrl, v)
   }
   ```
   
   wdyt?



##########
catalog/rest/rest_internal_test.go:
##########
@@ -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";,

Review Comment:
   The bad URL only goes into `defaults`, but the merge precedence is defaults 
< client props < overrides (`maps.Copy(cfg, rsp.Overrides)`), and `overrides` 
is the server-controlled case the user can't suppress — the more sensitive one. 
I'd add a sub-case that puts the malformed URL in `overrides` and asserts the 
same error, so both paths are pinned.
   
   If you take the "only fatal for overrides / only when a credential is set" 
suggestion from the `fetchConfig` thread, this test is also where you'd nail 
down that distinction.



##########
catalog/rest/rest.go:
##########
@@ -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 {

Review Comment:
   Two things on the server-config path here.
   
   A malformed value in the server's `defaults` now hard-fails the whole 
catalog load, even for a user who never configured a credential and has no 
OAuth intent at all. `defaults` are meant to be server suggestions a client can 
override, so failing init on one feels heavier than the threat warrants. I'd 
consider scoping the fatal behavior to `overrides`, or only erroring when 
`opts.credential != ""` — no credential, no misdirection risk.
   
   It's also worth a one-line comment on the threat model: `fetchConfig` 
already ran `setupOAuthManager` from the local opts before this point, so the 
server-supplied auth-url doesn't actually rebuild the OAuth manager in the 
current flow. This guard catches a misconfigured server early, which is useful, 
but the credential-misdirection scenario from the PR description is really the 
local-props path. Worth being explicit about which case this branch is 
defending.



##########
catalog/rest/rest_internal_test.go:
##########
@@ -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";,

Review Comment:
   `http://[::1` is doing a lot of documentation work here as the one 
malformation the current fix catches — a reader could easily assume `""` or 
`example.com/auth` are covered too, when they aren't. If we add the scheme/host 
validation, I'd extend this (or add a table case) to cover a scheme-less value, 
so the test communicates the actual contract.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to