git.iko.soy/typeable/oauth2-proxy
on master help sign in
Enable custom structure for group claim with default name group (#839)

* Allow complex structure for groups in group claim. * Remove unused constant * Update variable name * Fix linting * Use helper method * Log error if not possible to append group value * Add missing import * Use own logger * Fix imports * Remove Dockerfile for testing * Add Changelog entry * Use formatGroup helper method and update tests * Return string instead of string array * Remove groups variable * Return error in format method. * Reorder imports Co-authored-by: Nick Meves <nick.meves@greenhouse.io>

commit65016c8da155396ce68f7221d8e10703c89f0342 authorKevin Kreitner <kevinkreitner@gmx.de> dateTue 3 Nov 2020, 19:10 (6 years ago) parentcc6b808
3 files changed+91−14
CHANGELOG.md+1 providers/oidc.go+25−7 providers/oidc_test.go+65−7
CHANGELOG.md+1
@@ -11,6 +11,7 @@
1111 - Sessions from v6.0.0 or later had a graceful conversion to SHA256 that resulted in no reauthentication
1212 - Upgrading from v5.1.1 or earlier will result in a reauthentication
1313- [#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.
1415
1516## Breaking Changes
1617
providers/oidc.go+25−7
@@ -2,7 +2,9 @@ package providers
22
33import (
44 "context"
5+ "encoding/json"
56 "fmt"
7+ "reflect"
68 "strings"
79 "time"
810
@@ -10,6 +12,7 @@ import (
1012 "golang.org/x/oauth2"
1113
1214 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
15+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
1316 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
1417)
1518
@@ -285,21 +288,36 @@ func (p *OIDCProvider) extractGroupsFromRawClaims(rawClaims map[string]interface
285288 rawGroups, ok := rawClaims[p.GroupsClaim].([]interface{})
286289 if rawGroups != nil && ok {
287290 for _, rawGroup := range rawGroups {
288− group, ok := rawGroup.(string)
289− if ok {
290− groups = append(groups, group)
291+ formattedGroup, err := formatGroup(rawGroup)
292+ if err != nil {
293+ logger.Errorf("unable to format group of type %s with error %s", reflect.TypeOf(rawGroup), err)
294+ continue
291295 }
296+ groups = append(groups, formattedGroup)
292297 }
293298 }
294299
295300 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
296314}
297315
298316type OIDCClaims struct {
299317 rawClaims map[string]interface{}
300318 UserID string
301− Subject string `json:"sub"`
302− Verified *bool `json:"email_verified"`
303− PreferredUsername string `json:"preferred_username"`
304− Groups []string
319+ Subject string `json:"sub"`
320+ Verified *bool `json:"email_verified"`
321+ PreferredUsername string `json:"preferred_username"`
322+ Groups []string `json:"-"`
305323}
providers/oidc_test.go+65−7
@@ -29,12 +29,12 @@ const clientID = "https://test.myapp.com"
2929const secret = "secret"
3030
3131type idTokenClaims struct {
32− Name string `json:"name,omitempty"`
33− Email string `json:"email,omitempty"`
34− Phone string `json:"phone_number,omitempty"`
35− Picture string `json:"picture,omitempty"`
36− Groups []string `json:"groups,omitempty"`
37− OtherGroups []string `json:"other_groups,omitempty"`
32+ Name string `json:"name,omitempty"`
33+ Email string `json:"email,omitempty"`
34+ Phone string `json:"phone_number,omitempty"`
35+ Picture string `json:"picture,omitempty"`
36+ Groups interface{} `json:"groups,omitempty"`
37+ OtherGroups interface{} `json:"other_groups,omitempty"`
3838 jwt.StandardClaims
3939}
4040
@@ -64,6 +64,29 @@ var defaultIDToken idTokenClaims = idTokenClaims{
6464 },
6565}
6666
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+
6790var minimalIDToken idTokenClaims = idTokenClaims{
6891 "",
6992 "",
@@ -283,7 +306,7 @@ func TestCreateSessionStateFromBearerToken(t *testing.T) {
283306 GroupsClaim string
284307 ExpectedUser string
285308 ExpectedEmail string
286− ExpectedGroups []string
309+ ExpectedGroups interface{}
287310 }{
288311 "Default IDToken": {
289312 IDToken: defaultIDToken,
@@ -306,6 +329,13 @@ func TestCreateSessionStateFromBearerToken(t *testing.T) {
306329 ExpectedEmail: defaultIDToken.Email,
307330 ExpectedGroups: []string{"test:c", "test:d"},
308331 },
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+ },
309339 }
310340 for testName, tc := range testCases {
311341 t.Run(testName, func(t *testing.T) {
@@ -373,3 +403,31 @@ func TestOIDCProvider_findVerifiedIdToken(t *testing.T) {
373403 assert.Equal(t, nil, err)
374404 assert.Equal(t, true, verifiedIDToken == nil)
375405}
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+}