Commit 7cbf3d8d by Sean Johnson Committed by Marcus Efraimsson

OAuth: Fix role mapping from id token (#20300)

As part of the new improvements to JMESPath in #17149 a 
commit ensuring Role and Email data could be queried from 
id_token was lost.
This refactors and fixes extracting from both token and user 
info api consistently where before only either token or 
either user info api was used for extracting data/attributes.

Fixes #20243

Co-authored-by: Timo Wendt <timo@tjwendt.de>
Co-authored-by: twendt <timo@tjwendt.de>
Co-authored-by: henninge <henning@eggers.name>
Co-Authored-by: Henning Eggers <henning.eggers@inovex.de>
Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
parent fef31acb
package social package social
import ( import (
"github.com/grafana/grafana/pkg/infra/log" "encoding/json"
. "github.com/smartystreets/goconvey/convey" "io"
"net/http"
"net/http/httptest"
"time"
"github.com/stretchr/testify/require"
"testing" "testing"
"github.com/grafana/grafana/pkg/infra/log"
"golang.org/x/oauth2"
) )
func TestSearchJSONForEmail(t *testing.T) { func TestSearchJSONForEmail(t *testing.T) {
Convey("Given a generic OAuth provider", t, func() { t.Run("Given a generic OAuth provider", func(t *testing.T) {
provider := SocialGenericOAuth{ provider := SocialGenericOAuth{
SocialBase: &SocialBase{ SocialBase: &SocialBase{
log: log.New("generic_oauth_test"), log: log.New("generic_oauth_test"),
...@@ -77,16 +86,16 @@ func TestSearchJSONForEmail(t *testing.T) { ...@@ -77,16 +86,16 @@ func TestSearchJSONForEmail(t *testing.T) {
for _, test := range tests { for _, test := range tests {
provider.emailAttributePath = test.EmailAttributePath provider.emailAttributePath = test.EmailAttributePath
Convey(test.Name, func() { t.Run(test.Name, func(t *testing.T) {
actualResult := provider.searchJSONForAttr(test.EmailAttributePath, test.UserInfoJSONResponse) actualResult := provider.searchJSONForAttr(test.EmailAttributePath, test.UserInfoJSONResponse)
So(actualResult, ShouldEqual, test.ExpectedResult) require.Equal(t, test.ExpectedResult, actualResult)
}) })
} }
}) })
} }
func TestSearchJSONForRole(t *testing.T) { func TestSearchJSONForRole(t *testing.T) {
Convey("Given a generic OAuth provider", t, func() { t.Run("Given a generic OAuth provider", func(t *testing.T) {
provider := SocialGenericOAuth{ provider := SocialGenericOAuth{
SocialBase: &SocialBase{ SocialBase: &SocialBase{
log: log.New("generic_oauth_test"), log: log.New("generic_oauth_test"),
...@@ -131,9 +140,173 @@ func TestSearchJSONForRole(t *testing.T) { ...@@ -131,9 +140,173 @@ func TestSearchJSONForRole(t *testing.T) {
for _, test := range tests { for _, test := range tests {
provider.roleAttributePath = test.RoleAttributePath provider.roleAttributePath = test.RoleAttributePath
Convey(test.Name, func() { t.Run(test.Name, func(t *testing.T) {
actualResult := provider.searchJSONForAttr(test.RoleAttributePath, test.UserInfoJSONResponse) actualResult := provider.searchJSONForAttr(test.RoleAttributePath, test.UserInfoJSONResponse)
So(actualResult, ShouldEqual, test.ExpectedResult) require.Equal(t, test.ExpectedResult, actualResult)
})
}
})
}
func TestUserInfoSearchesForEmailAndRole(t *testing.T) {
t.Run("Given a generic OAuth provider", func(t *testing.T) {
provider := SocialGenericOAuth{
SocialBase: &SocialBase{
log: log.New("generic_oauth_test"),
},
emailAttributePath: "email",
}
tests := []struct {
Name string
APIURLReponse interface{}
OAuth2Extra interface{}
RoleAttributePath string
ExpectedEmail string
ExpectedRole string
}{
{
Name: "Given a valid id_token, a valid role path, no api response, use id_token",
OAuth2Extra: map[string]interface{}{
// { "role": "Admin", "email": "john.doe@example.com" }
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQWRtaW4iLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.9PtHcCaXxZa2HDlASyKIaFGfOKlw2ILQo32xlvhvhRg",
},
RoleAttributePath: "role",
ExpectedEmail: "john.doe@example.com",
ExpectedRole: "Admin",
},
{
Name: "Given a valid id_token, no role path, no api response, use id_token",
OAuth2Extra: map[string]interface{}{
// { "email": "john.doe@example.com" }
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.k5GwPcZvGe2BE_jgwN0ntz0nz4KlYhEd0hRRLApkTJ4",
},
RoleAttributePath: "",
ExpectedEmail: "john.doe@example.com",
ExpectedRole: "",
},
{
Name: "Given a valid id_token, an invalid role path, no api response, use id_token",
OAuth2Extra: map[string]interface{}{
// { "role": "Admin", "email": "john.doe@example.com" }
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQWRtaW4iLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.9PtHcCaXxZa2HDlASyKIaFGfOKlw2ILQo32xlvhvhRg",
},
RoleAttributePath: "invalid_path",
ExpectedEmail: "john.doe@example.com",
ExpectedRole: "",
},
{
Name: "Given no id_token, a valid role path, a valid api response, use api response",
APIURLReponse: map[string]interface{}{
"role": "Admin",
"email": "john.doe@example.com",
},
RoleAttributePath: "role",
ExpectedEmail: "john.doe@example.com",
ExpectedRole: "Admin",
},
{
Name: "Given no id_token, no role path, a valid api response, use api response",
APIURLReponse: map[string]interface{}{
"email": "john.doe@example.com",
},
RoleAttributePath: "",
ExpectedEmail: "john.doe@example.com",
ExpectedRole: "",
},
{
Name: "Given no id_token, a role path, a valid api response without a role, use api response",
APIURLReponse: map[string]interface{}{
"email": "john.doe@example.com",
},
RoleAttributePath: "role",
ExpectedEmail: "john.doe@example.com",
ExpectedRole: "",
},
{
Name: "Given no id_token, a valid role path, no api response, no data",
RoleAttributePath: "role",
ExpectedEmail: "",
ExpectedRole: "",
},
{
Name: "Given a valid id_token, a valid role path, a valid api response, prefer id_token",
OAuth2Extra: map[string]interface{}{
// { "role": "Admin", "email": "john.doe@example.com" }
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQWRtaW4iLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.9PtHcCaXxZa2HDlASyKIaFGfOKlw2ILQo32xlvhvhRg",
},
APIURLReponse: map[string]interface{}{
"role": "FromResponse",
"email": "from_response@example.com",
},
RoleAttributePath: "role",
ExpectedEmail: "john.doe@example.com",
ExpectedRole: "Admin",
},
{
Name: "Given a valid id_token, an invalid role path, a valid api response, prefer id_token",
OAuth2Extra: map[string]interface{}{
// { "role": "Admin", "email": "john.doe@example.com" }
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQWRtaW4iLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.9PtHcCaXxZa2HDlASyKIaFGfOKlw2ILQo32xlvhvhRg",
},
APIURLReponse: map[string]interface{}{
"role": "FromResponse",
"email": "from_response@example.com",
},
RoleAttributePath: "invalid_path",
ExpectedEmail: "john.doe@example.com",
ExpectedRole: "",
},
{
Name: "Given a valid id_token with no email, a valid role path, a valid api response with no role, merge",
OAuth2Extra: map[string]interface{}{
// { "role": "Admin" }
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQWRtaW4ifQ.k5GwPcZvGe2BE_jgwN0ntz0nz4KlYhEd0hRRLApkTJ4",
},
APIURLReponse: map[string]interface{}{
"email": "from_response@example.com",
},
RoleAttributePath: "role",
ExpectedEmail: "from_response@example.com",
ExpectedRole: "Admin",
},
{
Name: "Given a valid id_token with no role, a valid role path, a valid api response with no email, merge",
OAuth2Extra: map[string]interface{}{
// { "email": "john.doe@example.com" }
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.k5GwPcZvGe2BE_jgwN0ntz0nz4KlYhEd0hRRLApkTJ4",
},
APIURLReponse: map[string]interface{}{
"role": "FromResponse",
},
RoleAttributePath: "role",
ExpectedEmail: "john.doe@example.com",
ExpectedRole: "FromResponse",
},
}
for _, test := range tests {
provider.roleAttributePath = test.RoleAttributePath
t.Run(test.Name, func(t *testing.T) {
response, _ := json.Marshal(test.APIURLReponse)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
_, _ = io.WriteString(w, string(response))
}))
provider.apiUrl = ts.URL
staticToken := oauth2.Token{
AccessToken: "",
TokenType: "",
RefreshToken: "",
Expiry: time.Now(),
}
token := staticToken.WithExtra(test.OAuth2Extra)
actualResult, _ := provider.UserInfo(ts.Client(), token)
require.Equal(t, test.ExpectedEmail, actualResult.Email)
require.Equal(t, test.ExpectedEmail, actualResult.Login)
require.Equal(t, test.ExpectedRole, actualResult.Role)
}) })
} }
}) })
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment