git.iko.soy/typeable/oauth2-proxy
on master help sign in
Enable custom structure for group claim with default name group (#839)
65016c8Kevin Kreitner
CHANGELOG.md+1
@@ -11,6 +11,7 @@
11 - Sessions from v6.0.0 or later had a graceful conversion to SHA256 that resulted in no reauthentication
11 - Sessions from v6.0.0 or later had a graceful conversion to SHA256 that resulted in no reauthentication
12 - Upgrading from v5.1.1 or earlier will result in a reauthentication
12 - Upgrading from v5.1.1 or earlier will result in a reauthentication
13- [#616](https://github.com/oauth2-proxy/oauth2-proxy/pull/616) Ensure you have configured oauth2-proxy to use the `groups` scope. The user may be logged out initially as they may not currently have the `groups` claim however after going back through login process wil be authenticated.
13- [#616](https://github.com/oauth2-proxy/oauth2-proxy/pull/616) Ensure you have configured oauth2-proxy to use the `groups` scope. The user may be logged out initially as they may not currently have the `groups` claim however after going back through login process wil be authenticated.
14+- [#839](https://github.com/oauth2-proxy/oauth2-proxy/pull/839) Enables complex data structures for group claim entries, which are output as Json by default.
14
15
15## Breaking Changes
16## Breaking Changes
16
17
providers/oidc.go+25−7
@@ -2,7 +2,9 @@ package providers
2
2
3import (
3import (
4 "context"
4 "context"
5+ "encoding/json"
5 "fmt"
6 "fmt"
7+ "reflect"
6 "strings"
8 "strings"
7 "time"
9 "time"
8
10
@@ -10,6 +12,7 @@ import (
10 "golang.org/x/oauth2"
12 "golang.org/x/oauth2"
11
13
12 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
14 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
15+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
13 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
16 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
14)
17)
15
18
@@ -285,21 +288,36 @@ func (p *OIDCProvider) extractGroupsFromRawClaims(rawClaims map[string]interface
285 rawGroups, ok := rawClaims[p.GroupsClaim].([]interface{})
288 rawGroups, ok := rawClaims[p.GroupsClaim].([]interface{})
286 if rawGroups != nil && ok {
289 if rawGroups != nil && ok {
287 for _, rawGroup := range rawGroups {
290 for _, rawGroup := range rawGroups {
288− group, ok := rawGroup.(string)
291+ formattedGroup, err := formatGroup(rawGroup)
289− if ok {
292+ if err != nil {
290− groups = append(groups, group)
293+ logger.Errorf("unable to format group of type %s with error %s", reflect.TypeOf(rawGroup), err)
294+ continue
291 }
295 }
296+ groups = append(groups, formattedGroup)
292 }
297 }
293 }
298 }
294
299
295 return groups
300 return groups
301+
302+}
303+
304+func formatGroup(rawGroup interface{}) (string, error) {
305+ group, ok := rawGroup.(string)
306+ if !ok {
307+ jsonGroup, err := json.Marshal(rawGroup)
308+ if err != nil {
309+ return "", err
310+ }
311+ group = string(jsonGroup)
312+ }
313+ return group, nil
296}
314}
297
315
298type OIDCClaims struct {
316type OIDCClaims struct {
299 rawClaims map[string]interface{}
317 rawClaims map[string]interface{}
300 UserID string
318 UserID string
301− Subject string `json:"sub"`
319+ Subject string `json:"sub"`
302− Verified *bool `json:"email_verified"`
320+ Verified *bool `json:"email_verified"`
303− PreferredUsername string `json:"preferred_username"`
321+ PreferredUsername string `json:"preferred_username"`
304− Groups []string
322+ Groups []string `json:"-"`
305}
323}
providers/oidc_test.go+65−7
@@ -29,12 +29,12 @@ const clientID = "https://test.myapp.com"
29const secret = "secret"
29const secret = "secret"
30
30
31type idTokenClaims struct {
31type idTokenClaims struct {
32− Name string `json:"name,omitempty"`
32+ Name string `json:"name,omitempty"`
33− Email string `json:"email,omitempty"`
33+ Email string `json:"email,omitempty"`
34− Phone string `json:"phone_number,omitempty"`
34+ Phone string `json:"phone_number,omitempty"`
35− Picture string `json:"picture,omitempty"`
35+ Picture string `json:"picture,omitempty"`
36− Groups []string `json:"groups,omitempty"`
36+ Groups interface{} `json:"groups,omitempty"`
37− OtherGroups []string `json:"other_groups,omitempty"`
37+ OtherGroups interface{} `json:"other_groups,omitempty"`
38 jwt.StandardClaims
38 jwt.StandardClaims
39}
39}
40
40
@@ -64,6 +64,29 @@ var defaultIDToken idTokenClaims = idTokenClaims{
64 },
64 },
65}
65}
66
66
67+var customGroupClaimIDToken idTokenClaims = idTokenClaims{
68+ "Jane Dobbs",
69+ "janed@me.com",
70+ "+4798765432",
71+ "http://mugbook.com/janed/me.jpg",
72+ []map[string]interface{}{
73+ {
74+ "groupId": "Admin Group Id",
75+ "roles": []string{"Admin"},
76+ },
77+ },
78+ []string{"test:c", "test:d"},
79+ jwt.StandardClaims{
80+ Audience: "https://test.myapp.com",
81+ ExpiresAt: time.Now().Add(time.Duration(5) * time.Minute).Unix(),
82+ Id: "id-some-id",
83+ IssuedAt: time.Now().Unix(),
84+ Issuer: "https://issuer.example.com",
85+ NotBefore: 0,
86+ Subject: "123456789",
87+ },
88+}
89+
67var minimalIDToken idTokenClaims = idTokenClaims{
90var minimalIDToken idTokenClaims = idTokenClaims{
68 "",
91 "",
69 "",
92 "",
@@ -283,7 +306,7 @@ func TestCreateSessionStateFromBearerToken(t *testing.T) {
283 GroupsClaim string
306 GroupsClaim string
284 ExpectedUser string
307 ExpectedUser string
285 ExpectedEmail string
308 ExpectedEmail string
286− ExpectedGroups []string
309+ ExpectedGroups interface{}
287 }{
310 }{
288 "Default IDToken": {
311 "Default IDToken": {
289 IDToken: defaultIDToken,
312 IDToken: defaultIDToken,
@@ -306,6 +329,13 @@ func TestCreateSessionStateFromBearerToken(t *testing.T) {
306 ExpectedEmail: defaultIDToken.Email,
329 ExpectedEmail: defaultIDToken.Email,
307 ExpectedGroups: []string{"test:c", "test:d"},
330 ExpectedGroups: []string{"test:c", "test:d"},
308 },
331 },
332+ "Custom Groups Claim2": {
333+ IDToken: customGroupClaimIDToken,
334+ GroupsClaim: "groups",
335+ ExpectedUser: customGroupClaimIDToken.Subject,
336+ ExpectedEmail: customGroupClaimIDToken.Email,
337+ ExpectedGroups: []string{"{\"groupId\":\"Admin Group Id\",\"roles\":[\"Admin\"]}"},
338+ },
309 }
339 }
310 for testName, tc := range testCases {
340 for testName, tc := range testCases {
311 t.Run(testName, func(t *testing.T) {
341 t.Run(testName, func(t *testing.T) {
@@ -373,3 +403,31 @@ func TestOIDCProvider_findVerifiedIdToken(t *testing.T) {
373 assert.Equal(t, nil, err)
403 assert.Equal(t, nil, err)
374 assert.Equal(t, true, verifiedIDToken == nil)
404 assert.Equal(t, true, verifiedIDToken == nil)
375}
405}
406+
407+func Test_formatGroup(t *testing.T) {
408+ testCases := map[string]struct {
409+ RawGroup interface{}
410+ ExpectedFormattedGroupValue string
411+ }{
412+ "String Group": {
413+ RawGroup: "group",
414+ ExpectedFormattedGroupValue: "group",
415+ },
416+ "Map Group": {
417+ RawGroup: map[string]string{"id": "1", "name": "Test"},
418+ ExpectedFormattedGroupValue: "{\"id\":\"1\",\"name\":\"Test\"}",
419+ },
420+ "List Group": {
421+ RawGroup: []string{"First", "Second"},
422+ ExpectedFormattedGroupValue: "[\"First\",\"Second\"]",
423+ },
424+ }
425+
426+ for testName, tc := range testCases {
427+ t.Run(testName, func(t *testing.T) {
428+ formattedGroup, err := formatGroup(tc.RawGroup)
429+ assert.Nil(t, err)
430+ assert.Equal(t, tc.ExpectedFormattedGroupValue, formattedGroup)
431+ })
432+ }
433+}