mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-11-04 01:51:04 +01:00 
			
		
		
		
	fix issue where some oidc claim bools are sent as string (#2297)
Jumpcloud send invalid json, so we need to handle it. Fixes #2293 Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
		
							parent
							
								
									ec8729b772
								
							
						
					
					
						commit
						5345f19693
					
				@ -3,6 +3,8 @@ package types
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"cmp"
 | 
						"cmp"
 | 
				
			||||||
	"database/sql"
 | 
						"database/sql"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"net/mail"
 | 
						"net/mail"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -119,18 +121,49 @@ func (u *User) Proto() *v1.User {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// JumpCloud returns a 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
 | 
				
			||||||
 | 
					// ensure we can decode it from a string.
 | 
				
			||||||
 | 
					// https://github.com/juanfont/headscale/issues/2293
 | 
				
			||||||
 | 
					type FlexibleBoolean bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (bit *FlexibleBoolean) UnmarshalJSON(data []byte) error {
 | 
				
			||||||
 | 
						var val interface{}
 | 
				
			||||||
 | 
						err := json.Unmarshal(data, &val)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("could not unmarshal data: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch v := val.(type) {
 | 
				
			||||||
 | 
						case bool:
 | 
				
			||||||
 | 
							*bit = FlexibleBoolean(v)
 | 
				
			||||||
 | 
						case string:
 | 
				
			||||||
 | 
							pv, err := strconv.ParseBool(v)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("could not parse %s as boolean: %w", v, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							*bit = FlexibleBoolean(pv)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return fmt.Errorf("could not parse %v as boolean", v)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type OIDCClaims struct {
 | 
					type OIDCClaims struct {
 | 
				
			||||||
	// Sub is the user's unique identifier at the provider.
 | 
						// Sub is the user's unique identifier at the provider.
 | 
				
			||||||
	Sub string `json:"sub"`
 | 
						Sub string `json:"sub"`
 | 
				
			||||||
	Iss string `json:"iss"`
 | 
						Iss string `json:"iss"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Name is the user's full name.
 | 
						// Name is the user's full name.
 | 
				
			||||||
	Name              string   `json:"name,omitempty"`
 | 
						Name              string          `json:"name,omitempty"`
 | 
				
			||||||
	Groups            []string `json:"groups,omitempty"`
 | 
						Groups            []string        `json:"groups,omitempty"`
 | 
				
			||||||
	Email             string   `json:"email,omitempty"`
 | 
						Email             string          `json:"email,omitempty"`
 | 
				
			||||||
	EmailVerified     bool     `json:"email_verified,omitempty"`
 | 
						EmailVerified     FlexibleBoolean `json:"email_verified,omitempty"`
 | 
				
			||||||
	ProfilePictureURL string   `json:"picture,omitempty"`
 | 
						ProfilePictureURL string          `json:"picture,omitempty"`
 | 
				
			||||||
	Username          string   `json:"preferred_username,omitempty"`
 | 
						Username          string          `json:"preferred_username,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *OIDCClaims) Identifier() string {
 | 
					func (c *OIDCClaims) Identifier() string {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										75
									
								
								hscontrol/types/users_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								hscontrol/types/users_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					package types
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/google/go-cmp/cmp"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestUnmarshallOIDCClaims(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name    string
 | 
				
			||||||
 | 
							jsonstr string
 | 
				
			||||||
 | 
							want    OIDCClaims
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "normal-bool",
 | 
				
			||||||
 | 
								jsonstr: `
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "sub": "test",
 | 
				
			||||||
 | 
					  "email": "test@test.no",
 | 
				
			||||||
 | 
					  "email_verified": true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
								`,
 | 
				
			||||||
 | 
								want: OIDCClaims{
 | 
				
			||||||
 | 
									Sub:           "test",
 | 
				
			||||||
 | 
									Email:         "test@test.no",
 | 
				
			||||||
 | 
									EmailVerified: true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "string-bool-true",
 | 
				
			||||||
 | 
								jsonstr: `
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "sub": "test2",
 | 
				
			||||||
 | 
					  "email": "test2@test.no",
 | 
				
			||||||
 | 
					  "email_verified": "true"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
								`,
 | 
				
			||||||
 | 
								want: OIDCClaims{
 | 
				
			||||||
 | 
									Sub:           "test2",
 | 
				
			||||||
 | 
									Email:         "test2@test.no",
 | 
				
			||||||
 | 
									EmailVerified: true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "string-bool-false",
 | 
				
			||||||
 | 
								jsonstr: `
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "sub": "test3",
 | 
				
			||||||
 | 
					  "email": "test3@test.no",
 | 
				
			||||||
 | 
					  "email_verified": "false"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
								`,
 | 
				
			||||||
 | 
								want: OIDCClaims{
 | 
				
			||||||
 | 
									Sub:           "test3",
 | 
				
			||||||
 | 
									Email:         "test3@test.no",
 | 
				
			||||||
 | 
									EmailVerified: false,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								var got OIDCClaims
 | 
				
			||||||
 | 
								if err := json.Unmarshal([]byte(tt.jsonstr), &got); err != nil {
 | 
				
			||||||
 | 
									t.Errorf("UnmarshallOIDCClaims() error = %v", err)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if diff := cmp.Diff(got, tt.want); diff != "" {
 | 
				
			||||||
 | 
									t.Errorf("UnmarshallOIDCClaims() mismatch (-want +got):\n%s", diff)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user