Repository: oauth2-proxy/oauth2-proxy
Pull request #850 (2020-10-19 02:00:36)

Add User & Groups to Userinfo
https://github.com/oauth2-proxy/oauth2-proxy/issues/834

## Description

Add User & Groups fields from the Session to the `/oauth2/userinfo` endpoint.  Previously only `email` and `preferredUsername` were included.

`groups` is set to `omitempty` in the JSON tag as most installations won't use the `allowed-groups` authorization configuration.

## How Has This Been Tested?

Unit Tests

## Checklist:

- [x] My change requires a change to the documentation or CHANGELOG.
- [x] I have updated the documentation/CHANGELOG accordingly.
- [x] I have created a feature (non-master) branch for my PR.


NickMeves: > LGTM, would prefer to have a couple of different test cases rather than just the one, but I'll leave that up to you if you want to add more or leave for another day

Good call -- I'll add a few variants later today when I rebase.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index efb8ed24..5047a8c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -55,6 +55,7 @@
 - [#797](https://github.com/oauth2-proxy/oauth2-proxy/pull/797) Create universal Authorization behavior across providers (@NickMeves)
 - [#898](https://github.com/oauth2-proxy/oauth2-proxy/pull/898) Migrate documentation to Docusaurus (@JoelSpeed)
 - [#754](https://github.com/oauth2-proxy/oauth2-proxy/pull/754) Azure token refresh (@codablock)
+- [#850](https://github.com/oauth2-proxy/oauth2-proxy/pull/850) Increase session fields in `/oauth2/userinfo` endpoint (@NickMeves)
 - [#825](https://github.com/oauth2-proxy/oauth2-proxy/pull/825) Fix code coverage reporting on GitHub actions(@JoelSpeed)
 - [#796](https://github.com/oauth2-proxy/oauth2-proxy/pull/796) Deprecate GetUserName & GetEmailAdress for EnrichSessionState (@NickMeves)
 - [#705](https://github.com/oauth2-proxy/oauth2-proxy/pull/705) Add generic Header injectors for upstream request and response headers (@JoelSpeed)
diff --git a/oauthproxy.go b/oauthproxy.go
index 343c6ec9..28df21f4 100644
--- a/oauthproxy.go
+++ b/oauthproxy.go
@@ -798,13 +798,19 @@ func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {
 		http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
 		return
 	}
+
 	userInfo := struct {
-		Email             string `json:"email"`
-		PreferredUsername string `json:"preferredUsername,omitempty"`
+		User              string   `json:"user"`
+		Email             string   `json:"email"`
+		Groups            []string `json:"groups,omitempty"`
+		PreferredUsername string   `json:"preferredUsername,omitempty"`
 	}{
+		User:              session.User,
 		Email:             session.Email,
+		Groups:            session.Groups,
 		PreferredUsername: session.PreferredUsername,
 	}
+
 	rw.Header().Set("Content-Type", "application/json")
 	rw.WriteHeader(http.StatusOK)
 	err = json.NewEncoder(rw).Encode(userInfo)


Test patch:
diff --git a/oauthproxy_test.go b/oauthproxy_test.go
index a2733f6d..6ed4b30c 100644
--- a/oauthproxy_test.go
+++ b/oauthproxy_test.go
@@ -1124,20 +1124,67 @@ func NewUserInfoEndpointTest() (*ProcessCookieTest, error) {
 }
 
 func TestUserInfoEndpointAccepted(t *testing.T) {
-	test, err := NewUserInfoEndpointTest()
-	if err != nil {
-		t.Fatal(err)
+	testCases := []struct {
+		name             string
+		session          *sessions.SessionState
+		expectedResponse string
+	}{
+		{
+			name: "Full session",
+			session: &sessions.SessionState{
+				User:        "john.doe",
+				Email:       "john.doe@example.com",
+				Groups:      []string{"example", "groups"},
+				AccessToken: "my_access_token",
+			},
+			expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n",
+		},
+		{
+			name: "Minimal session",
+			session: &sessions.SessionState{
+				User:   "john.doe",
+				Email:  "john.doe@example.com",
+				Groups: []string{"example", "groups"},
+			},
+			expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"]}\n",
+		},
+		{
+			name: "No groups",
+			session: &sessions.SessionState{
+				User:        "john.doe",
+				Email:       "john.doe@example.com",
+				AccessToken: "my_access_token",
+			},
+			expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\"}\n",
+		},
+		{
+			name: "With Preferred Username",
+			session: &sessions.SessionState{
+				User:              "john.doe",
+				PreferredUsername: "john",
+				Email:             "john.doe@example.com",
+				Groups:            []string{"example", "groups"},
+				AccessToken:       "my_access_token",
+			},
+			expectedResponse: "{\"user\":\"john.doe\",\"email\":\"john.doe@example.com\",\"groups\":[\"example\",\"groups\"],\"preferredUsername\":\"john\"}\n",
+		},
 	}
 
-	startSession := &sessions.SessionState{
-		Email: "john.doe@example.com", AccessToken: "my_access_token"}
-	err = test.SaveSession(startSession)
-	assert.NoError(t, err)
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			test, err := NewUserInfoEndpointTest()
+			if err != nil {
+				t.Fatal(err)
+			}
+			err = test.SaveSession(tc.session)
+			assert.NoError(t, err)
 
-	test.proxy.ServeHTTP(test.rw, test.req)
-	assert.Equal(t, http.StatusOK, test.rw.Code)
-	bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
-	assert.Equal(t, "{\"email\":\"john.doe@example.com\"}\n", string(bodyBytes))
+			test.proxy.ServeHTTP(test.rw, test.req)
+			assert.Equal(t, http.StatusOK, test.rw.Code)
+			bodyBytes, _ := ioutil.ReadAll(test.rw.Body)
+			assert.Equal(t, tc.expectedResponse, string(bodyBytes))
+		})
+	}
 }
 
 func TestUserInfoEndpointUnauthorizedOnNoCookieSetError(t *testing.T) {