@@ -3,7 +3,6 @@ package util
33import (
44 "context"
55 "encoding/base64"
6− "encoding/json"
76 "fmt"
87 "mime"
98 "net/http"
@@ -12,17 +11,17 @@ import (
1211
1312 "github.com/bitly/go-simplejson"
1413 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
15− "github.com/spf13/cast"
14+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
1615)
1716
1817// ClaimExtractor is used to extract claim values from an ID Token, or, if not
1918// present, from the profile URL.
2019type ClaimExtractor interface {
2120 // GetClaim fetches a named claim and returns the value.
22− GetClaim(claim string) (interface{}, bool, error)
21+ GetClaim(claim string) (any, bool, error)
2322
2423 // GetClaimInto fetches a named claim and puts the value into the destination.
25− GetClaimInto(claim string, dst interface{}) (bool, error)
24+ GetClaimInto(claim string, dst any) (bool, error)
2625}
2726
2827// NewClaimExtractor constructs a new ClaimExtractor from the raw ID Token.
@@ -31,12 +30,12 @@ type ClaimExtractor interface {
3130func NewClaimExtractor(ctx context.Context, idToken string, profileURL *url.URL, profileRequestHeaders http.Header) (ClaimExtractor, error) {
3231 payload, err := parseJWT(idToken)
3332 if err != nil {
34− return nil, fmt.Errorf("failed to parse ID Token: %v", err)
33+ return nil, fmt.Errorf("failed to parse ID Token: %w", err)
3534 }
3635
3736 tokenClaims, err := simplejson.NewJson(payload)
3837 if err != nil {
39− return nil, fmt.Errorf("failed to parse ID Token payload: %v", err)
38+ return nil, fmt.Errorf("failed to parse ID Token payload: %w", err)
4039 }
4140
4241 return &claimExtractor{
@@ -59,7 +58,7 @@ type claimExtractor struct {
5958// GetClaim will return the value claim if it exists.
6059// It will only return an error if the profile URL needs to be fetched due to
6160// the claim not being present in the ID Token.
62−func (c *claimExtractor) GetClaim(claim string) (interface{}, bool, error) {
61+func (c *claimExtractor) GetClaim(claim string) (any, bool, error) {
6362 if claim == "" {
6463 return nil, false, nil
6564 }
@@ -124,7 +123,7 @@ func (c *claimExtractor) loadProfileClaims() (*simplejson.Json, error) {
124123// GetClaimInto loads a claim and places it into the destination interface.
125124// This will attempt to coerce the claim into the specified type.
126125// If it cannot be coerced, an error may be returned.
127−func (c *claimExtractor) GetClaimInto(claim string, dst interface{}) (bool, error) {
126+func (c *claimExtractor) GetClaimInto(claim string, dst any) (bool, error) {
128127 value, exists, err := c.GetClaim(claim)
129128 if err != nil {
130129 return false, fmt.Errorf("could not get claim %q: %v", claim, err)
@@ -132,8 +131,8 @@ func (c *claimExtractor) GetClaimInto(claim string, dst interface{}) (bool, erro
132131 if !exists {
133132 return false, nil
134133 }
135− if err := coerceClaim(value, dst); err != nil {
136− return false, fmt.Errorf("could no coerce claim: %v", err)
134+ if err := util.CoerceClaim(value, dst); err != nil {
135+ return false, fmt.Errorf("could not coerce claim: %v", err)
137136 }
138137
139138 return true, nil
@@ -156,73 +155,10 @@ func parseJWT(p string) ([]byte, error) {
156155// getClaimFrom gets a claim from a Json object.
157156// It can accept either a single claim name or a json path. The claim is always evaluated first as a single claim name.
158157// Paths with indexes are not supported.
159−func getClaimFrom(claim string, src *simplejson.Json) interface{} {
158+func getClaimFrom(claim string, src *simplejson.Json) any {
160159 if value, ok := src.CheckGet(claim); ok {
161160 return value.Interface()
162161 }
163162 claimParts := strings.Split(claim, ".")
164163 return src.GetPath(claimParts...).Interface()
165164}
166−
167−// coerceClaim tries to convert the value into the destination interface type.
168−// If it can convert the value, it will then store the value in the destination
169−// interface.
170−func coerceClaim(value, dst interface{}) error {
171− switch d := dst.(type) {
172− case *string:
173− str, err := toString(value)
174− if err != nil {
175− return fmt.Errorf("could not convert value to string: %v", err)
176− }
177− *d = str
178− case *[]string:
179− strSlice, err := toStringSlice(value)
180− if err != nil {
181− return fmt.Errorf("could not convert value to string slice: %v", err)
182− }
183− *d = strSlice
184− case *bool:
185− *d = cast.ToBool(value)
186− default:
187− return fmt.Errorf("unknown type for destination: %T", dst)
188− }
189− return nil
190−}
191−
192−// toStringSlice converts an interface (either a slice or single value) into
193−// a slice of strings.
194−func toStringSlice(value interface{}) ([]string, error) {
195− var sliceValues []interface{}
196− switch v := value.(type) {
197− case []interface{}:
198− sliceValues = v
199− case interface{}:
200− sliceValues = []interface{}{v}
201− default:
202− sliceValues = cast.ToSlice(value)
203− }
204−
205− out := []string{}
206− for _, v := range sliceValues {
207− str, err := toString(v)
208− if err != nil {
209− return nil, fmt.Errorf("could not convert slice entry to string %v: %v", v, err)
210− }
211− out = append(out, str)
212− }
213− return out, nil
214−}
215−
216−// toString coerces a value into a string.
217−// If it is non-string, marshal it into JSON.
218−func toString(value interface{}) (string, error) {
219− if str, err := cast.ToStringE(value); err == nil {
220− return str, nil
221− }
222−
223− jsonStr, err := json.Marshal(value)
224− if err != nil {
225− return "", err
226− }
227− return string(jsonStr), nil
228−}