Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions management/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -1420,16 +1420,24 @@ type ConnectionOptionsOAuth2 struct {

// UnmarshalJSON implements the json.Unmarshaler interface for ConnectionOptionsOAuth2.
// It is required to handle differences in the scope field, which can
// be an array of strings or a single string.
// be an array of strings or a single string, and to handle the customHeaders
// field, which legacy Auth0 tenants may return as a JSON-encoded string instead
// of a map[string]string object.
func (c *ConnectionOptionsOAuth2) UnmarshalJSON(data []byte) error {
type connectionOptionsOAuth2 ConnectionOptionsOAuth2

// RawCustomHeaders shadows the embedded CustomHeaders *map[string]string field
// so that encoding/json places the raw value here (as interface{}) rather than
// attempting to decode it directly into *map[string]string. This prevents a
// "cannot unmarshal string into Go struct field ... of type map[string]string"
// error for connections whose customHeaders were stored in a legacy format.
type connectionOptionsOAuth2Wrapper struct {
*connectionOptionsOAuth2
RawScope interface{} `json:"scope,omitempty"`
RawScope interface{} `json:"scope,omitempty"`
RawCustomHeaders interface{} `json:"customHeaders,omitempty"`
}

alias := &connectionOptionsOAuth2Wrapper{(*connectionOptionsOAuth2)(c), nil}
alias := &connectionOptionsOAuth2Wrapper{(*connectionOptionsOAuth2)(c), nil, nil}

err := json.Unmarshal(data, alias)
if err != nil {
Expand All @@ -1452,6 +1460,29 @@ func (c *ConnectionOptionsOAuth2) UnmarshalJSON(data []byte) error {
}
}

// Normalise customHeaders: accept both the current map[string]interface{} form
// and the legacy string form (a JSON-encoded value from an older schema version).
// In the legacy string case we discard the value β€” it cannot be reliably decoded
// without knowing the original structure β€” so custom_headers will appear empty
// and can be set manually in the Terraform config.
if alias.RawCustomHeaders != nil {
switch v := alias.RawCustomHeaders.(type) {
case map[string]interface{}:
headers := make(map[string]string, len(v))
for key, val := range v {
if s, ok := val.(string); ok {
headers[key] = s
}
}
if len(headers) > 0 {
c.CustomHeaders = &headers
}
case string:
// Legacy format: discard. The provider will surface custom_headers as
// empty; the operator can restore the values in their Terraform config.
}
}

return nil
}

Expand Down
20 changes: 20 additions & 0 deletions management/connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1630,12 +1630,32 @@ func TestOAuth2Connection_UnmarshalJSON(t *testing.T) {
`{"scope":null}`: {Scope: nil},
`{}`: {},
`{"scope":[]}`: {Scope: auth0.String("")},
`{"customHeaders":{"X-Foo":"bar","X-Baz":"qux"}}`: {
CustomHeaders: &map[string]string{"X-Foo": "bar", "X-Baz": "qux"},
},
} {
var actual *ConnectionOptionsOAuth2
err := json.Unmarshal([]byte(expectedAsString), &actual)
assert.NoError(t, err)
assert.Equal(t, expected, actual)
}

t.Run("It handles a legacy string-encoded customHeaders value without error", func(t *testing.T) {
// Some older Auth0 tenants stored customHeaders as a JSON-encoded string
// rather than a map. The unmarshaler must not return an error in this case;
// instead it discards the value so the caller can restore it via config.
var actual *ConnectionOptionsOAuth2
err := json.Unmarshal([]byte(`{"customHeaders":"{\"X-Foo\":\"bar\"}"}`), &actual)
assert.NoError(t, err)
assert.Nil(t, actual.CustomHeaders)
})

t.Run("It handles a null customHeaders value without error", func(t *testing.T) {
var actual *ConnectionOptionsOAuth2
err := json.Unmarshal([]byte(`{"customHeaders":null}`), &actual)
assert.NoError(t, err)
assert.Nil(t, actual.CustomHeaders)
})
}

func TestGoogleOauth2Connection_MarshalJSON(t *testing.T) {
Expand Down