From a6ed64caafbcfd23ed33e19dc8468d79f952c2fc Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Tue, 16 Jun 2026 16:51:55 +0200 Subject: [PATCH] Stop source-name loop on Google 403 A token without admin.directory.customer.readonly makes Customers.Get return 403 forever. Treat that as terminal so the source-name worker keeps the generic name and stops retrying. Signed-off-by: Bryan Frimin --- .../drivers/google_workspace_test.go | 63 +++++++++++++++++++ pkg/accessreview/drivers/name_resolver.go | 7 +++ 2 files changed, 70 insertions(+) diff --git a/pkg/accessreview/drivers/google_workspace_test.go b/pkg/accessreview/drivers/google_workspace_test.go index 6a7f9bc22..d9ebfc837 100644 --- a/pkg/accessreview/drivers/google_workspace_test.go +++ b/pkg/accessreview/drivers/google_workspace_test.go @@ -16,6 +16,8 @@ package drivers import ( "context" + "net/http" + "net/http/httptest" "os" "testing" @@ -40,3 +42,64 @@ func TestGoogleWorkspaceDriver(t *testing.T) { assert.NotEmpty(t, r.ExternalID) assert.NotEmpty(t, r.Roles) } + +func TestGoogleWorkspaceNameResolver(t *testing.T) { + t.Parallel() + + cases := []struct { + name string + status int + body string + want string + wantErr bool + }{ + { + name: "200 returns customer domain", + status: http.StatusOK, + body: `{"kind":"admin#directory#customer","customerDomain":"example.com"}`, + want: "example.com", + }, + { + name: "403 is terminal (no error, no name)", + status: http.StatusForbidden, + body: `{"error":{"code":403,"message":"Not Authorized to access this resource/api","status":"FORBIDDEN"}}`, + want: "", + }, + { + name: "500 is retryable", + status: http.StatusInternalServerError, + body: `{"error":{"code":500,"message":"Internal Server Error","status":"INTERNAL"}}`, + wantErr: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + srv := httptest.NewServer( + http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + assert.Equal(t, "/admin/directory/v1/customers/my_customer", r.URL.Path) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(tc.status) + _, _ = w.Write([]byte(tc.body)) + }, + ), + ) + defer srv.Close() + + client := &http.Client{Transport: &hostRewriter{target: srv.URL}} + + got, err := NewGoogleWorkspaceNameResolver(client).ResolveInstanceName(t.Context()) + if tc.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/pkg/accessreview/drivers/name_resolver.go b/pkg/accessreview/drivers/name_resolver.go index 2280e8f8a..8bd418c77 100644 --- a/pkg/accessreview/drivers/name_resolver.go +++ b/pkg/accessreview/drivers/name_resolver.go @@ -18,12 +18,14 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "net/http" "net/url" "go.probo.inc/probo/pkg/connector" admin "google.golang.org/api/admin/directory/v1" + "google.golang.org/api/googleapi" "google.golang.org/api/option" ) @@ -87,6 +89,11 @@ func (r *googleWorkspaceNameResolver) ResolveInstanceName(ctx context.Context) ( customer, err := adminService.Customers.Get("my_customer").Context(ctx).Do() if err != nil { + var gerr *googleapi.Error + if errors.As(err, &gerr) && gerr.Code == http.StatusForbidden { + return "", nil + } + return "", fmt.Errorf("cannot fetch google workspace customer: %w", err) }