diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 30119b671..4ffe633bb 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -155,9 +155,22 @@ type UserProfile struct { LoginName string // "alice@smith.com"; for display purposes only (provider is not listed) DisplayName string // "Alice Smith" ProfilePicURL string - Roles []RoleID // deprecated; clients should not rely on Roles + + // Roles exists for legacy reasons, to keep old macOS clients + // happy. It JSON marshals as []. + Roles emptyStructJSONSlice +} + +type emptyStructJSONSlice struct{} + +var emptyJSONSliceBytes = []byte("[]") + +func (emptyStructJSONSlice) MarshalJSON() ([]byte, error) { + return emptyJSONSliceBytes, nil } +func (emptyStructJSONSlice) UnmarshalJSON([]byte) error { return nil } + type Node struct { ID NodeID StableID StableNodeID diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index d679810d0..bc1c2d023 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -6,6 +6,7 @@ package tailcfg import ( "encoding" + "encoding/json" "reflect" "strings" "testing" @@ -476,3 +477,25 @@ func TestCloneNode(t *testing.T) { }) } } + +func TestUserProfileJSONMarshalForMac(t *testing.T) { + // Old macOS clients had a bug where they required + // UserProfile.Roles to be non-null. Lock that in + // 1.0.x/1.2.x clients are gone in the wild. + // See mac commit 0242c08a2ca496958027db1208f44251bff8488b (Sep 30). + // It was fixed in at least 1.4.x, and perhaps 1.2.x. + j, err := json.Marshal(UserProfile{}) + if err != nil { + t.Fatal(err) + } + const wantSub = `"Roles":[]` + if !strings.Contains(string(j), wantSub) { + t.Fatalf("didn't contain %#q; got: %s", wantSub, j) + } + + // And back: + var up UserProfile + if err := json.Unmarshal(j, &up); err != nil { + t.Fatalf("Unmarshal: %v", err) + } +}