mirror of
https://github.com/juanfont/headscale.git
synced 2026-05-04 19:46:12 +02:00
oidc: handle groups claim as string or array (FlexibleStringSlice)
Some OIDC providers (notably JumpCloud) return the `groups` claim as
a plain string when the user belongs to a single group, rather than
a single-element array:
Single group: {"groups": "MyGroup"}
Multiple groups: {"groups": ["Group1", "Group2"]}
This causes `json.Unmarshal` to fail with:
cannot unmarshal string into Go struct field OIDCClaims.groups of type []string
This is the same class of issue as juanfont#2293 (FlexibleBoolean for
email_verified). The fix follows the same pattern: introduce a
FlexibleStringSlice type with a custom UnmarshalJSON that accepts
both a string and a []string, and use it for the Groups field in
both OIDCClaims and OIDCUserInfo.
This commit is contained in:
parent
76ee29352b
commit
3d0f597b23
@ -24,6 +24,9 @@ import (
|
||||
// ErrCannotParseBoolean is returned when a value cannot be parsed as boolean.
|
||||
var ErrCannotParseBoolean = errors.New("cannot parse value as boolean")
|
||||
|
||||
// ErrCannotParseStringSlice is returned when a value cannot be parsed as string or []string.
|
||||
var ErrCannotParseStringSlice = errors.New("cannot parse value as string or []string")
|
||||
|
||||
type UserID uint64
|
||||
|
||||
type Users []User
|
||||
@ -229,6 +232,29 @@ func (u UserView) MarshalZerologObject(e *zerolog.Event) {
|
||||
u.ж.MarshalZerologObject(e)
|
||||
}
|
||||
|
||||
// FlexibleStringSlice handles OIDC providers (e.g. JumpCloud) that return the
|
||||
// groups claim as a plain string when the user belongs to a single group,
|
||||
// instead of a single-element array.
|
||||
type FlexibleStringSlice []string
|
||||
|
||||
func (f *FlexibleStringSlice) UnmarshalJSON(data []byte) error {
|
||||
var arr []string
|
||||
err := json.Unmarshal(data, &arr)
|
||||
if err == nil {
|
||||
*f = arr
|
||||
return nil
|
||||
}
|
||||
|
||||
var single string
|
||||
err = json.Unmarshal(data, &single)
|
||||
if err == nil {
|
||||
*f = []string{single}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: %s", ErrCannotParseStringSlice, string(data))
|
||||
}
|
||||
|
||||
// FlexibleBoolean handles JumpCloud's JSON where email_verified is returned as a
|
||||
// string "true" or "false" instead of a boolean.
|
||||
// This maps bool to a specific type with a custom unmarshaler to
|
||||
@ -269,9 +295,9 @@ type OIDCClaims struct {
|
||||
|
||||
// Name is the user's full name.
|
||||
Name string `json:"name,omitempty"`
|
||||
Groups []string `json:"groups,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
EmailVerified FlexibleBoolean `json:"email_verified,omitempty"`
|
||||
Groups FlexibleStringSlice `json:"groups,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
EmailVerified FlexibleBoolean `json:"email_verified,omitempty"`
|
||||
ProfilePictureURL string `json:"picture,omitempty"`
|
||||
Username string `json:"preferred_username,omitempty"`
|
||||
}
|
||||
@ -387,9 +413,9 @@ type OIDCUserInfo struct {
|
||||
FamilyName string `json:"family_name"`
|
||||
PreferredUsername string `json:"preferred_username"`
|
||||
Email string `json:"email"`
|
||||
EmailVerified FlexibleBoolean `json:"email_verified,omitempty"`
|
||||
Groups []string `json:"groups"`
|
||||
Picture string `json:"picture"`
|
||||
EmailVerified FlexibleBoolean `json:"email_verified,omitempty"`
|
||||
Groups FlexibleStringSlice `json:"groups"`
|
||||
Picture string `json:"picture"`
|
||||
}
|
||||
|
||||
// FromClaim overrides a User from OIDC claims.
|
||||
|
||||
@ -61,6 +61,57 @@ func TestUnmarshallOIDCClaims(t *testing.T) {
|
||||
EmailVerified: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "groups-array",
|
||||
jsonstr: `
|
||||
{
|
||||
"sub": "test4",
|
||||
"email": "test4@test.no",
|
||||
"email_verified": true,
|
||||
"groups": ["Group1", "Group2"]
|
||||
}
|
||||
`,
|
||||
want: OIDCClaims{
|
||||
Sub: "test4",
|
||||
Email: "test4@test.no",
|
||||
EmailVerified: true,
|
||||
Groups: FlexibleStringSlice{"Group1", "Group2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "groups-single-string",
|
||||
jsonstr: `
|
||||
{
|
||||
"sub": "test5",
|
||||
"email": "test5@test.no",
|
||||
"email_verified": true,
|
||||
"groups": "SingleGroup"
|
||||
}
|
||||
`,
|
||||
want: OIDCClaims{
|
||||
Sub: "test5",
|
||||
Email: "test5@test.no",
|
||||
EmailVerified: true,
|
||||
Groups: FlexibleStringSlice{"SingleGroup"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "groups-empty-array",
|
||||
jsonstr: `
|
||||
{
|
||||
"sub": "test6",
|
||||
"email": "test6@test.no",
|
||||
"email_verified": true,
|
||||
"groups": []
|
||||
}
|
||||
`,
|
||||
want: OIDCClaims{
|
||||
Sub: "test6",
|
||||
Email: "test6@test.no",
|
||||
EmailVerified: true,
|
||||
Groups: FlexibleStringSlice{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user