mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2026-10-18 13:18:26 +02:00
feat: allow arbitrary claims from the IDToken and IdentityProvider UserInfo endpoint to be added to the session state (#2685)
* feat: support additional claims Signed-off-by: afsu <suaf2020@163.com> Signed-off-by: af su <saf@zjuici.com> * docs: clarify that AdditionalClaims may come from id_token or userinfo endpoint Signed-off-by: afsu <suaf2020@163.com> Signed-off-by: af su <saf@zjuici.com> * feat: include AdditionalClaims in /oauth2/userinfo response (#834) Signed-off-by: afsu <suaf2020@163.com> Signed-off-by: af su <saf@zjuici.com> * refactor: extract coerceClaim logic into util Signed-off-by: afsu <suaf2020@163.com> Signed-off-by: af su <saf@zjuici.com> * doc: add changelog entry for #2685 Signed-off-by: Jan Larwig <jan@larwig.com> * refactor: added more verbose comments to some struct fields and minor code cleanup Signed-off-by: Jan Larwig <jan@larwig.com> --------- Signed-off-by: afsu <suaf2020@163.com> Signed-off-by: af su <saf@zjuici.com> Signed-off-by: Jan Larwig <jan@larwig.com> Co-authored-by: af su <saf@zjuici.com> Co-authored-by: Jan Larwig <jan@larwig.com>
This commit is contained in:
co-authored by
af su
Jan Larwig
parent
c6355ee402
commit
e59f7c1549
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
@@ -12,6 +13,8 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
func GetCertPool(paths []string, useSystemPool bool) (*x509.CertPool, error) {
|
||||
@@ -191,3 +194,64 @@ func RemoveDuplicateStr(strSlice []string) []string {
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// CoerceClaim tries to convert the value into the destination interface type.
|
||||
// If it can convert the value, it will then store the value in the destination
|
||||
// interface.
|
||||
func CoerceClaim(value, dst any) error {
|
||||
switch d := dst.(type) {
|
||||
case *string:
|
||||
str, err := toString(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not convert value to string: %v", err)
|
||||
}
|
||||
*d = str
|
||||
case *[]string:
|
||||
strSlice, err := toStringSlice(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not convert value to string slice: %v", err)
|
||||
}
|
||||
*d = strSlice
|
||||
case *bool:
|
||||
*d = cast.ToBool(value)
|
||||
default:
|
||||
return fmt.Errorf("unknown type for destination: %T", dst)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// toStringSlice converts an interface (either a slice or single value) into
|
||||
// a slice of strings.
|
||||
func toStringSlice(value any) ([]string, error) {
|
||||
var sliceValues []any
|
||||
switch v := value.(type) {
|
||||
case []any:
|
||||
sliceValues = v
|
||||
default:
|
||||
sliceValues = []any{v}
|
||||
}
|
||||
|
||||
out := []string{}
|
||||
for _, v := range sliceValues {
|
||||
str, err := toString(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not convert slice entry to string %v: %v", v, err)
|
||||
}
|
||||
out = append(out, str)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// toString coerces a value into a string.
|
||||
// If it is non-string, marshal it into JSON.
|
||||
func toString(value any) (string, error) {
|
||||
if str, err := cast.ToStringE(value); err == nil {
|
||||
return str, nil
|
||||
}
|
||||
|
||||
jsonStr, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(jsonStr), nil
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ package util
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util/ptr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -253,3 +256,70 @@ func TestGetCertPool(t *testing.T) {
|
||||
assert.Error(t, err3)
|
||||
}
|
||||
}
|
||||
|
||||
type coerceClaimTableInput struct {
|
||||
name string
|
||||
value any
|
||||
dst any
|
||||
expectedDst any
|
||||
expectedError error
|
||||
}
|
||||
|
||||
func TestCoerceClaim(t *testing.T) {
|
||||
tests := []coerceClaimTableInput{
|
||||
{
|
||||
name: "coerces a string to a string",
|
||||
value: "some_string",
|
||||
dst: ptr.To(""),
|
||||
expectedDst: ptr.To("some_string"),
|
||||
},
|
||||
{
|
||||
name: "coerces a slice to a string slice",
|
||||
value: []any{"a", "b"},
|
||||
dst: ptr.To([]string{}),
|
||||
expectedDst: ptr.To([]string{"a", "b"}),
|
||||
},
|
||||
{
|
||||
name: "coerces a bool to a bool",
|
||||
value: true,
|
||||
dst: ptr.To(false),
|
||||
expectedDst: ptr.To(true),
|
||||
},
|
||||
{
|
||||
name: "coerces a string to a bool",
|
||||
value: "true",
|
||||
dst: ptr.To(false),
|
||||
expectedDst: ptr.To(true),
|
||||
},
|
||||
{
|
||||
name: "coerces a map to a string",
|
||||
value: map[string]any{
|
||||
"foo": []any{"bar", "baz"},
|
||||
},
|
||||
dst: ptr.To(""),
|
||||
expectedDst: ptr.To("{\"foo\":[\"bar\",\"baz\"]}"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := CoerceClaim(tt.value, tt.dst)
|
||||
if tt.expectedError != nil {
|
||||
if err == nil || err.Error() != tt.expectedError.Error() {
|
||||
t.Errorf("expected error %v, got %v", tt.expectedError, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.dst, tt.expectedDst) {
|
||||
gotJSON, _ := json.Marshal(tt.dst)
|
||||
wantJSON, _ := json.Marshal(tt.expectedDst)
|
||||
t.Errorf("expected dst to be %+v, got %+v", string(wantJSON), string(gotJSON))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user