Skip to content

Commit 44d4646

Browse files
angrycubEmyrk
andauthored
fix: defensively handle nil maps and slices in marshaling (#18418)
Adds a custom marshaler to handle some cases where nils were being marshaled to nulls, causing the web UI to throw an error. --------- Co-authored-by: Steven Masley <stevenmasley@gmail.com>
1 parent 9cbe02e commit 44d4646

File tree

5 files changed

+80
-0
lines changed

5 files changed

+80
-0
lines changed

coderd/idpsync/group.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,17 @@ func (s *GroupSyncSettings) String() string {
274274
return runtimeconfig.JSONString(s)
275275
}
276276

277+
func (s *GroupSyncSettings) MarshalJSON() ([]byte, error) {
278+
if s.Mapping == nil {
279+
s.Mapping = make(map[string][]uuid.UUID)
280+
}
281+
282+
// Aliasing the struct to avoid infinite recursion when calling json.Marshal
283+
// on the struct itself.
284+
type Alias GroupSyncSettings
285+
return json.Marshal(&struct{ *Alias }{Alias: (*Alias)(s)})
286+
}
287+
277288
type ExpectedGroup struct {
278289
OrganizationID uuid.UUID
279290
GroupID *uuid.UUID

coderd/idpsync/idpsync_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,57 @@ package idpsync_test
22

33
import (
44
"encoding/json"
5+
"regexp"
56
"testing"
67

78
"github.com/stretchr/testify/require"
89

910
"github.com/coder/coder/v2/coderd/idpsync"
1011
)
1112

13+
// TestMarshalJSONEmpty ensures no empty maps are marshaled as `null` in JSON.
14+
func TestMarshalJSONEmpty(t *testing.T) {
15+
t.Parallel()
16+
17+
t.Run("Group", func(t *testing.T) {
18+
t.Parallel()
19+
20+
output, err := json.Marshal(&idpsync.GroupSyncSettings{
21+
RegexFilter: regexp.MustCompile(".*"),
22+
})
23+
require.NoError(t, err, "marshal empty group settings")
24+
require.NotContains(t, string(output), "null")
25+
26+
require.JSONEq(t,
27+
`{"field":"","mapping":{},"regex_filter":".*","auto_create_missing_groups":false}`,
28+
string(output))
29+
})
30+
31+
t.Run("Role", func(t *testing.T) {
32+
t.Parallel()
33+
34+
output, err := json.Marshal(&idpsync.RoleSyncSettings{})
35+
require.NoError(t, err, "marshal empty group settings")
36+
require.NotContains(t, string(output), "null")
37+
38+
require.JSONEq(t,
39+
`{"field":"","mapping":{}}`,
40+
string(output))
41+
})
42+
43+
t.Run("Organization", func(t *testing.T) {
44+
t.Parallel()
45+
46+
output, err := json.Marshal(&idpsync.OrganizationSyncSettings{})
47+
require.NoError(t, err, "marshal empty group settings")
48+
require.NotContains(t, string(output), "null")
49+
50+
require.JSONEq(t,
51+
`{"field":"","mapping":{},"assign_default":false}`,
52+
string(output))
53+
})
54+
}
55+
1256
func TestParseStringSliceClaim(t *testing.T) {
1357
t.Parallel()
1458

coderd/idpsync/organization.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,17 @@ func (s *OrganizationSyncSettings) String() string {
234234
return runtimeconfig.JSONString(s)
235235
}
236236

237+
func (s *OrganizationSyncSettings) MarshalJSON() ([]byte, error) {
238+
if s.Mapping == nil {
239+
s.Mapping = make(map[string][]uuid.UUID)
240+
}
241+
242+
// Aliasing the struct to avoid infinite recursion when calling json.Marshal
243+
// on the struct itself.
244+
type Alias OrganizationSyncSettings
245+
return json.Marshal(&struct{ *Alias }{Alias: (*Alias)(s)})
246+
}
247+
237248
// ParseClaims will parse the claims and return the list of organizations the user
238249
// should sync to.
239250
func (s *OrganizationSyncSettings) ParseClaims(ctx context.Context, db database.Store, mergedClaims jwt.MapClaims) ([]uuid.UUID, error) {

coderd/idpsync/role.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,14 @@ func (s *RoleSyncSettings) String() string {
291291
}
292292
return runtimeconfig.JSONString(s)
293293
}
294+
295+
func (s *RoleSyncSettings) MarshalJSON() ([]byte, error) {
296+
if s.Mapping == nil {
297+
s.Mapping = make(map[string][]string)
298+
}
299+
300+
// Aliasing the struct to avoid infinite recursion when calling json.Marshal
301+
// on the struct itself.
302+
type Alias RoleSyncSettings
303+
return json.Marshal(&struct{ *Alias }{Alias: (*Alias)(s)})
304+
}

enterprise/coderd/idpsync.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,9 @@ func (api *API) idpSyncClaimFieldValues(orgID uuid.UUID, rw http.ResponseWriter,
836836
httpapi.InternalServerError(rw, err)
837837
return
838838
}
839+
if fieldValues == nil {
840+
fieldValues = []string{}
841+
}
839842

840843
httpapi.Write(ctx, rw, http.StatusOK, fieldValues)
841844
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy