Skip to content

Commit b43b1e9

Browse files
claims: add JSON serialization for interface arrays
Implement handling of []interface{} types by serializing them to JSON string format. This allows arrays like ["role1", "role2"] to be converted to string representations for further processing.
1 parent 59cef11 commit b43b1e9

File tree

3 files changed

+155
-5
lines changed

3 files changed

+155
-5
lines changed

.changelog/26958.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:improvement
2+
oidc: add support for array-based OIDC claims
3+
```

lib/auth/claims.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,23 @@ func getClaim(all map[string]interface{}, claim string) interface{} {
152152
// stringifyClaimValue will try to convert the provided raw value into a
153153
// faithful string representation of that value per these rules:
154154
//
155-
// - strings => unchanged
156-
// - bool => "true" / "false"
157-
// - json.Number => String()
158-
// - float32/64 => truncated to int64 and then formatted as an ascii string
159-
// - intXX/uintXX => casted to int64 and then formatted as an ascii string
155+
// - []interface{} => marshaling to JSON string
156+
// - strings => unchanged
157+
// - bool => "true" / "false"
158+
// - json.Number => String()
159+
// - float32/64 => truncated to int64 and then formatted as an ascii string
160+
// - intXX/uintXX => casted to int64 and then formatted as an ascii string
160161
//
161162
// If successful the string value and true are returned. otherwise an empty
162163
// string and false are returned.
163164
func stringifyClaimValue(rawValue interface{}) (string, bool) {
164165
switch v := rawValue.(type) {
166+
case []interface{}:
167+
b, err := json.Marshal(v)
168+
if err != nil {
169+
return "", false
170+
}
171+
return string(b), true
165172
case string:
166173
return v, true
167174
case bool:

lib/auth/claims_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ package auth
66
import (
77
"testing"
88

9+
"encoding/json"
10+
11+
"strconv"
12+
913
"github.com/shoenig/test/must"
1014

1115
"github.com/hashicorp/nomad/nomad/structs"
@@ -87,3 +91,139 @@ func TestSelectorData(t *testing.T) {
8791
})
8892
}
8993
}
94+
95+
func TestStringifyClaimValue(t *testing.T) {
96+
cases := []struct {
97+
Name string
98+
Input interface{}
99+
Expected string
100+
OK bool
101+
}{
102+
{
103+
Name: "string",
104+
Input: "hello",
105+
Expected: "hello",
106+
OK: true,
107+
},
108+
{
109+
Name: "bool true",
110+
Input: true,
111+
Expected: "true",
112+
OK: true,
113+
},
114+
{
115+
Name: "bool false",
116+
Input: false,
117+
Expected: "false",
118+
OK: true,
119+
},
120+
{
121+
Name: "json.Number",
122+
Input: json.Number("12345"),
123+
Expected: "12345",
124+
OK: true,
125+
},
126+
{
127+
Name: "float64",
128+
Input: float64(42.99),
129+
Expected: strconv.FormatInt(42, 10),
130+
OK: true,
131+
},
132+
{
133+
Name: "float32",
134+
Input: float32(99.9),
135+
Expected: strconv.FormatInt(99, 10),
136+
OK: true,
137+
},
138+
{
139+
Name: "int",
140+
Input: int(123),
141+
Expected: "123",
142+
OK: true,
143+
},
144+
{
145+
Name: "int8",
146+
Input: int8(-8),
147+
Expected: "-8",
148+
OK: true,
149+
},
150+
{
151+
Name: "int16",
152+
Input: int16(16),
153+
Expected: "16",
154+
OK: true,
155+
},
156+
{
157+
Name: "int32",
158+
Input: int32(32),
159+
Expected: "32",
160+
OK: true,
161+
},
162+
{
163+
Name: "int64",
164+
Input: int64(64),
165+
Expected: "64",
166+
OK: true,
167+
},
168+
{
169+
Name: "uint",
170+
Input: uint(321),
171+
Expected: "321",
172+
OK: true,
173+
},
174+
{
175+
Name: "uint8",
176+
Input: uint8(8),
177+
Expected: "8",
178+
OK: true,
179+
},
180+
{
181+
Name: "uint16",
182+
Input: uint16(16),
183+
Expected: "16",
184+
OK: true,
185+
},
186+
{
187+
Name: "uint32",
188+
Input: uint32(32),
189+
Expected: "32",
190+
OK: true,
191+
},
192+
{
193+
Name: "uint64",
194+
Input: uint64(64),
195+
Expected: "64",
196+
OK: true,
197+
},
198+
{
199+
Name: "slice of interface{}",
200+
Input: []interface{}{
201+
"foo", 42, true,
202+
},
203+
Expected: `["foo",42,true]`,
204+
OK: true,
205+
},
206+
{
207+
Name: "unknown type",
208+
Input: struct{ X int }{X: 1},
209+
Expected: "",
210+
OK: false,
211+
},
212+
{
213+
Name: "invalid JSON slice element",
214+
Input: []interface{}{
215+
func() {},
216+
},
217+
Expected: "",
218+
OK: false,
219+
},
220+
}
221+
222+
for _, tt := range cases {
223+
t.Run(tt.Name, func(t *testing.T) {
224+
out, ok := stringifyClaimValue(tt.Input)
225+
must.Eq(t, tt.OK, ok)
226+
must.Eq(t, tt.Expected, out)
227+
})
228+
}
229+
}

0 commit comments

Comments
 (0)