2023-12-08 11:20:42 +01:00
package connectors
2019-08-26 12:11:40 -04:00
import (
2023-06-14 12:30:40 +00:00
"context"
2020-01-18 04:57:24 +11:00
"encoding/json"
"net/http"
"net/http/httptest"
2021-11-12 10:22:04 +01:00
"testing"
2020-01-18 04:57:24 +11:00
"time"
2021-11-12 10:22:04 +01:00
"github.com/stretchr/testify/assert"
2020-01-18 04:57:24 +11:00
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
2022-01-13 13:30:28 -05:00
2023-12-08 11:20:42 +01:00
"github.com/grafana/grafana/pkg/login/social"
2023-11-20 09:45:40 +01:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2022-09-15 17:35:59 +02:00
"github.com/grafana/grafana/pkg/services/org"
2023-12-08 11:20:42 +01:00
"github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests"
2023-11-20 09:45:40 +01:00
"github.com/grafana/grafana/pkg/setting"
)
2020-11-05 11:00:00 +01:00
2019-08-26 12:11:40 -04:00
func TestSearchJSONForEmail ( t * testing . T ) {
2020-01-18 04:57:24 +11:00
t . Run ( "Given a generic OAuth provider" , func ( t * testing . T ) {
2023-12-08 11:20:42 +01:00
provider := NewGenericOAuthProvider ( social . NewOAuthInfo ( ) , & setting . Cfg { } , & ssosettingstests . MockService { } , featuremgmt . WithFeatures ( ) )
2019-08-26 12:11:40 -04:00
tests := [ ] struct {
Name string
UserInfoJSONResponse [ ] byte
EmailAttributePath string
ExpectedResult string
2020-04-02 17:35:48 +03:00
ExpectedError string
2019-08-26 12:11:40 -04:00
} {
{
Name : "Given an invalid user info JSON response" ,
UserInfoJSONResponse : [ ] byte ( "{" ) ,
EmailAttributePath : "attributes.email" ,
ExpectedResult : "" ,
2020-04-02 17:35:48 +03:00
ExpectedError : "failed to unmarshal user info JSON response: unexpected end of JSON input" ,
2019-08-26 12:11:40 -04:00
} ,
{
Name : "Given an empty user info JSON response and empty JMES path" ,
UserInfoJSONResponse : [ ] byte { } ,
EmailAttributePath : "" ,
ExpectedResult : "" ,
2020-04-02 17:35:48 +03:00
ExpectedError : "no attribute path specified" ,
2019-08-26 12:11:40 -04:00
} ,
{
Name : "Given an empty user info JSON response and valid JMES path" ,
UserInfoJSONResponse : [ ] byte { } ,
EmailAttributePath : "attributes.email" ,
ExpectedResult : "" ,
2020-04-02 17:35:48 +03:00
ExpectedError : "empty user info JSON response provided" ,
2019-08-26 12:11:40 -04:00
} ,
{
Name : "Given a simple user info JSON response and valid JMES path" ,
UserInfoJSONResponse : [ ] byte ( ` {
"attributes" : {
"email" : "grafana@localhost"
}
} ` ) ,
EmailAttributePath : "attributes.email" ,
ExpectedResult : "grafana@localhost" ,
} ,
{
Name : "Given a user info JSON response with e-mails array and valid JMES path" ,
UserInfoJSONResponse : [ ] byte ( ` {
"attributes" : {
"emails" : [ "grafana@localhost" , "admin@localhost" ]
}
} ` ) ,
EmailAttributePath : "attributes.emails[0]" ,
ExpectedResult : "grafana@localhost" ,
} ,
{
Name : "Given a nested user info JSON response and valid JMES path" ,
UserInfoJSONResponse : [ ] byte ( ` {
"identities" : [
{
"userId" : "grafana@localhost"
} ,
{
"userId" : "admin@localhost"
}
]
} ` ) ,
EmailAttributePath : "identities[0].userId" ,
ExpectedResult : "grafana@localhost" ,
} ,
}
for _ , test := range tests {
provider . emailAttributePath = test . EmailAttributePath
2020-01-18 04:57:24 +11:00
t . Run ( test . Name , func ( t * testing . T ) {
2021-07-01 22:40:46 +02:00
actualResult , err := provider . searchJSONForStringAttr ( test . EmailAttributePath , test . UserInfoJSONResponse )
if test . ExpectedError == "" {
require . NoError ( t , err , "Testing case %q" , test . Name )
} else {
require . EqualError ( t , err , test . ExpectedError , "Testing case %q" , test . Name )
}
require . Equal ( t , test . ExpectedResult , actualResult )
} )
}
} )
}
func TestSearchJSONForGroups ( t * testing . T ) {
t . Run ( "Given a generic OAuth provider" , func ( t * testing . T ) {
2023-12-08 11:20:42 +01:00
provider := NewGenericOAuthProvider ( social . NewOAuthInfo ( ) , & setting . Cfg { } , & ssosettingstests . MockService { } , featuremgmt . WithFeatures ( ) )
2021-07-01 22:40:46 +02:00
tests := [ ] struct {
Name string
UserInfoJSONResponse [ ] byte
GroupsAttributePath string
ExpectedResult [ ] string
ExpectedError string
} {
{
Name : "Given an invalid user info JSON response" ,
UserInfoJSONResponse : [ ] byte ( "{" ) ,
GroupsAttributePath : "attributes.groups" ,
ExpectedResult : [ ] string { } ,
ExpectedError : "failed to unmarshal user info JSON response: unexpected end of JSON input" ,
} ,
{
Name : "Given an empty user info JSON response and empty JMES path" ,
UserInfoJSONResponse : [ ] byte { } ,
GroupsAttributePath : "" ,
ExpectedResult : [ ] string { } ,
ExpectedError : "no attribute path specified" ,
} ,
{
Name : "Given an empty user info JSON response and valid JMES path" ,
UserInfoJSONResponse : [ ] byte { } ,
GroupsAttributePath : "attributes.groups" ,
ExpectedResult : [ ] string { } ,
ExpectedError : "empty user info JSON response provided" ,
} ,
{
Name : "Given a simple user info JSON response and valid JMES path" ,
UserInfoJSONResponse : [ ] byte ( ` {
"attributes" : {
"groups" : [ "foo" , "bar" ]
}
} ` ) ,
GroupsAttributePath : "attributes.groups[]" ,
ExpectedResult : [ ] string { "foo" , "bar" } ,
} ,
}
for _ , test := range tests {
provider . groupsAttributePath = test . GroupsAttributePath
t . Run ( test . Name , func ( t * testing . T ) {
actualResult , err := provider . searchJSONForStringArrayAttr ( test . GroupsAttributePath , test . UserInfoJSONResponse )
2020-04-02 17:35:48 +03:00
if test . ExpectedError == "" {
require . NoError ( t , err , "Testing case %q" , test . Name )
} else {
require . EqualError ( t , err , test . ExpectedError , "Testing case %q" , test . Name )
}
2020-01-18 04:57:24 +11:00
require . Equal ( t , test . ExpectedResult , actualResult )
2019-11-05 21:56:42 +01:00
} )
}
} )
}
func TestSearchJSONForRole ( t * testing . T ) {
2020-01-18 04:57:24 +11:00
t . Run ( "Given a generic OAuth provider" , func ( t * testing . T ) {
2023-12-08 11:20:42 +01:00
provider := NewGenericOAuthProvider ( social . NewOAuthInfo ( ) , & setting . Cfg { } , & ssosettingstests . MockService { } , featuremgmt . WithFeatures ( ) )
2019-11-05 21:56:42 +01:00
tests := [ ] struct {
Name string
UserInfoJSONResponse [ ] byte
RoleAttributePath string
ExpectedResult string
2020-04-02 17:35:48 +03:00
ExpectedError string
2019-11-05 21:56:42 +01:00
} {
{
Name : "Given an invalid user info JSON response" ,
UserInfoJSONResponse : [ ] byte ( "{" ) ,
RoleAttributePath : "attributes.role" ,
ExpectedResult : "" ,
2020-04-02 17:35:48 +03:00
ExpectedError : "failed to unmarshal user info JSON response: unexpected end of JSON input" ,
2019-11-05 21:56:42 +01:00
} ,
{
Name : "Given an empty user info JSON response and empty JMES path" ,
UserInfoJSONResponse : [ ] byte { } ,
RoleAttributePath : "" ,
ExpectedResult : "" ,
2020-04-02 17:35:48 +03:00
ExpectedError : "no attribute path specified" ,
2019-11-05 21:56:42 +01:00
} ,
{
Name : "Given an empty user info JSON response and valid JMES path" ,
UserInfoJSONResponse : [ ] byte { } ,
RoleAttributePath : "attributes.role" ,
ExpectedResult : "" ,
2020-04-02 17:35:48 +03:00
ExpectedError : "empty user info JSON response provided" ,
2019-11-05 21:56:42 +01:00
} ,
{
Name : "Given a simple user info JSON response and valid JMES path" ,
UserInfoJSONResponse : [ ] byte ( ` {
"attributes" : {
"role" : "admin"
}
} ` ) ,
RoleAttributePath : "attributes.role" ,
ExpectedResult : "admin" ,
} ,
}
for _ , test := range tests {
provider . roleAttributePath = test . RoleAttributePath
2020-01-18 04:57:24 +11:00
t . Run ( test . Name , func ( t * testing . T ) {
2021-07-01 22:40:46 +02:00
actualResult , err := provider . searchJSONForStringAttr ( test . RoleAttributePath , test . UserInfoJSONResponse )
2020-04-02 17:35:48 +03:00
if test . ExpectedError == "" {
require . NoError ( t , err , "Testing case %q" , test . Name )
} else {
require . EqualError ( t , err , test . ExpectedError , "Testing case %q" , test . Name )
}
2020-01-18 04:57:24 +11:00
require . Equal ( t , test . ExpectedResult , actualResult )
} )
}
} )
}
func TestUserInfoSearchesForEmailAndRole ( t * testing . T ) {
2023-12-08 11:20:42 +01:00
provider := NewGenericOAuthProvider ( & social . OAuthInfo {
EmailAttributePath : "email" ,
} , & setting . Cfg { } ,
& ssosettingstests . MockService { } ,
featuremgmt . WithFeatures ( ) )
2020-01-18 04:57:24 +11:00
2023-07-17 15:58:16 +02:00
tests := [ ] struct {
Name string
SkipOrgRoleSync bool
AllowAssignGrafanaAdmin bool
2023-08-30 08:46:47 -07:00
ResponseBody any
OAuth2Extra any
2023-07-17 15:58:16 +02:00
RoleAttributePath string
ExpectedEmail string
ExpectedRole org . RoleType
ExpectedError error
ExpectedGrafanaAdmin * bool
} {
{
Name : "Given a valid id_token, a valid role path, no API response, use id_token" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2023-07-17 15:58:16 +02:00
// { "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" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2023-07-17 15:58:16 +02:00
// { "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.k5GwPcZvGe2BE_jgwN0ntz0nz4KlYhEd0hRRLApkTJ4" ,
} ,
RoleAttributePath : "" ,
ExpectedEmail : "john.doe@example.com" ,
ExpectedRole : "Viewer" ,
} ,
{
Name : "Given a valid id_token, an invalid role path, no API response, use id_token" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2023-07-17 15:58:16 +02:00
// { "role": "Admin", "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQWRtaW4iLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.9PtHcCaXxZa2HDlASyKIaFGfOKlw2ILQo32xlvhvhRg" ,
} ,
RoleAttributePath : "invalid_path" ,
ExpectedEmail : "john.doe@example.com" ,
ExpectedRole : "Viewer" ,
} ,
{
Name : "Given no id_token, a valid role path, a valid API response, use API response" ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2023-07-17 15:58:16 +02:00
"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" ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2023-07-17 15:58:16 +02:00
"email" : "john.doe@example.com" ,
2020-01-18 04:57:24 +11:00
} ,
2023-07-17 15:58:16 +02:00
RoleAttributePath : "" ,
ExpectedEmail : "john.doe@example.com" ,
ExpectedRole : "Viewer" ,
} ,
{
Name : "Given no id_token, a role path, a valid API response without a role, use API response" ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2023-07-17 15:58:16 +02:00
"email" : "john.doe@example.com" ,
2020-01-18 04:57:24 +11:00
} ,
2023-07-17 15:58:16 +02:00
RoleAttributePath : "role" ,
ExpectedEmail : "john.doe@example.com" ,
ExpectedRole : "Viewer" ,
} ,
{
Name : "Given no id_token, a valid role path, no API response, no data" ,
RoleAttributePath : "role" ,
ExpectedEmail : "" ,
ExpectedRole : "Viewer" ,
} ,
{
Name : "Given a valid id_token, a valid role path, a valid API response, prefer id_token" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2023-07-17 15:58:16 +02:00
// { "role": "Admin", "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQWRtaW4iLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.9PtHcCaXxZa2HDlASyKIaFGfOKlw2ILQo32xlvhvhRg" ,
} ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2023-07-17 15:58:16 +02:00
"role" : "FromResponse" ,
"email" : "from_response@example.com" ,
} ,
RoleAttributePath : "role" ,
ExpectedEmail : "john.doe@example.com" ,
ExpectedRole : "Admin" ,
} ,
{
Name : "Given a valid id_token and AssignGrafanaAdmin is unchecked, don't grant Server Admin" ,
AllowAssignGrafanaAdmin : false ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2023-07-17 15:58:16 +02:00
// { "role": "GrafanaAdmin", "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiR3JhZmFuYUFkbWluIiwiZW1haWwiOiJqb2huLmRvZUBleGFtcGxlLmNvbSJ9.cQqMJpVjwdtJ8qEZLOo9RKNbAFfpkQcpnRG0nopmWEI" ,
} ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2023-07-17 15:58:16 +02:00
"role" : "FromResponse" ,
"email" : "from_response@example.com" ,
} ,
RoleAttributePath : "role" ,
ExpectedEmail : "john.doe@example.com" ,
ExpectedRole : "Admin" ,
ExpectedGrafanaAdmin : nil ,
} ,
{
Name : "Given a valid id_token and AssignGrafanaAdmin is checked, grant Server Admin" ,
AllowAssignGrafanaAdmin : true ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2023-07-17 15:58:16 +02:00
// { "role": "GrafanaAdmin", "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiR3JhZmFuYUFkbWluIiwiZW1haWwiOiJqb2huLmRvZUBleGFtcGxlLmNvbSJ9.cQqMJpVjwdtJ8qEZLOo9RKNbAFfpkQcpnRG0nopmWEI" ,
} ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2023-07-17 15:58:16 +02:00
"role" : "FromResponse" ,
"email" : "from_response@example.com" ,
} ,
RoleAttributePath : "role" ,
ExpectedEmail : "john.doe@example.com" ,
ExpectedRole : "Admin" ,
ExpectedGrafanaAdmin : trueBoolPtr ( ) ,
} ,
{
Name : "Given a valid id_token, an invalid role path, a valid API response, prefer id_token" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2023-07-17 15:58:16 +02:00
// { "role": "Admin", "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQWRtaW4iLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.9PtHcCaXxZa2HDlASyKIaFGfOKlw2ILQo32xlvhvhRg" ,
} ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2023-07-17 15:58:16 +02:00
"role" : "FromResponse" ,
"email" : "from_response@example.com" ,
} ,
RoleAttributePath : "invalid_path" ,
ExpectedEmail : "john.doe@example.com" ,
ExpectedRole : "Viewer" ,
} ,
{
Name : "Given a valid id_token with no email, a valid role path, a valid API response with no role, merge" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2023-07-17 15:58:16 +02:00
// { "role": "Admin" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQWRtaW4ifQ.k5GwPcZvGe2BE_jgwN0ntz0nz4KlYhEd0hRRLApkTJ4" ,
2020-01-18 04:57:24 +11:00
} ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2023-07-17 15:58:16 +02:00
"email" : "from_response@example.com" ,
2021-09-14 02:15:15 +10:00
} ,
2023-07-17 15:58:16 +02:00
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" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2023-07-17 15:58:16 +02:00
// { "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.k5GwPcZvGe2BE_jgwN0ntz0nz4KlYhEd0hRRLApkTJ4" ,
} ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2023-07-17 15:58:16 +02:00
"role" : "FromResponse" ,
} ,
RoleAttributePath : "role" ,
ExpectedEmail : "john.doe@example.com" ,
ExpectedRole : "Viewer" ,
ExpectedError : nil ,
} ,
{
Name : "Given a valid id_token, a valid advanced JMESPath role path, derive the role" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2023-07-17 15:58:16 +02:00
// { "email": "john.doe@example.com",
// "info": { "roles": [ "dev", "engineering" ] }}
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIiwiaW5mbyI6eyJyb2xlcyI6WyJkZXYiLCJlbmdpbmVlcmluZyJdfX0.RmmQfv25eXb4p3wMrJsvXfGQ6EXhGtwRXo6SlCFHRNg" ,
} ,
RoleAttributePath : "contains(info.roles[*], 'dev') && 'Editor'" ,
ExpectedEmail : "john.doe@example.com" ,
ExpectedRole : "Editor" ,
} ,
{
Name : "Given a valid id_token without role info, a valid advanced JMESPath role path, a valid API response, derive the correct role using the userinfo API response (JMESPath warning on id_token)" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2023-07-17 15:58:16 +02:00
// { "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.k5GwPcZvGe2BE_jgwN0ntz0nz4KlYhEd0hRRLApkTJ4" ,
} ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
"info" : map [ string ] any {
2023-07-17 15:58:16 +02:00
"roles" : [ ] string { "engineering" , "SRE" } ,
2021-09-14 02:15:15 +10:00
} ,
} ,
2023-07-17 15:58:16 +02:00
RoleAttributePath : "contains(info.roles[*], 'SRE') && 'Admin'" ,
ExpectedEmail : "john.doe@example.com" ,
ExpectedRole : "Admin" ,
} ,
{
Name : "Given a valid id_token, a valid advanced JMESPath role path, a valid API response, prefer ID token" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2023-07-17 15:58:16 +02:00
// { "email": "john.doe@example.com",
// "info": { "roles": [ "dev", "engineering" ] }}
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIiwiaW5mbyI6eyJyb2xlcyI6WyJkZXYiLCJlbmdpbmVlcmluZyJdfX0.RmmQfv25eXb4p3wMrJsvXfGQ6EXhGtwRXo6SlCFHRNg" ,
} ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
"info" : map [ string ] any {
2023-07-17 15:58:16 +02:00
"roles" : [ ] string { "engineering" , "SRE" } ,
2021-09-14 02:15:15 +10:00
} ,
} ,
2023-07-17 15:58:16 +02:00
RoleAttributePath : "contains(info.roles[*], 'SRE') && 'Admin' || contains(info.roles[*], 'dev') && 'Editor' || 'Viewer'" ,
ExpectedEmail : "john.doe@example.com" ,
ExpectedRole : "Editor" ,
} ,
{
Name : "Given skip org role sync set to true, with a valid id_token, a valid advanced JMESPath role path, a valid API response, no org role should be set" ,
SkipOrgRoleSync : true ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2023-07-17 15:58:16 +02:00
// { "email": "john.doe@example.com",
// "info": { "roles": [ "dev", "engineering" ] }}
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIiwiaW5mbyI6eyJyb2xlcyI6WyJkZXYiLCJlbmdpbmVlcmluZyJdfX0.RmmQfv25eXb4p3wMrJsvXfGQ6EXhGtwRXo6SlCFHRNg" ,
} ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
"info" : map [ string ] any {
2023-07-17 15:58:16 +02:00
"roles" : [ ] string { "engineering" , "SRE" } ,
2023-02-01 16:27:53 +00:00
} ,
} ,
2023-07-17 15:58:16 +02:00
RoleAttributePath : "contains(info.roles[*], 'SRE') && 'Admin' || contains(info.roles[*], 'dev') && 'Editor' || 'Viewer'" ,
ExpectedEmail : "john.doe@example.com" ,
ExpectedRole : "" ,
} ,
}
2020-01-18 04:57:24 +11:00
2023-07-17 15:58:16 +02:00
for _ , test := range tests {
provider . roleAttributePath = test . RoleAttributePath
provider . allowAssignGrafanaAdmin = test . AllowAssignGrafanaAdmin
2023-12-15 10:58:08 +01:00
provider . info . SkipOrgRoleSync = test . SkipOrgRoleSync
2022-09-08 12:11:00 +02:00
2023-07-17 15:58:16 +02:00
t . Run ( test . Name , func ( t * testing . T ) {
body , err := json . Marshal ( test . ResponseBody )
require . NoError ( t , err )
ts := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( http . StatusOK )
w . Header ( ) . Set ( "Content-Type" , "application/json" )
_ , err = w . Write ( body )
2020-06-01 17:11:25 +02:00
require . NoError ( t , err )
2023-07-17 15:58:16 +02:00
} ) )
provider . apiUrl = ts . URL
staticToken := oauth2 . Token {
AccessToken : "" ,
TokenType : "" ,
RefreshToken : "" ,
Expiry : time . Now ( ) ,
}
2020-01-18 04:57:24 +11:00
2023-07-17 15:58:16 +02:00
token := staticToken . WithExtra ( test . OAuth2Extra )
actualResult , err := provider . UserInfo ( context . Background ( ) , ts . Client ( ) , token )
if test . ExpectedError != nil {
require . ErrorIs ( t , err , test . ExpectedError )
return
}
require . NoError ( t , err )
require . Equal ( t , test . ExpectedEmail , actualResult . Email )
require . Equal ( t , test . ExpectedEmail , actualResult . Login )
require . Equal ( t , test . ExpectedRole , actualResult . Role )
require . Equal ( t , test . ExpectedGrafanaAdmin , actualResult . IsGrafanaAdmin )
} )
}
2019-08-26 12:11:40 -04:00
}
2020-08-03 17:33:27 +03:00
func TestUserInfoSearchesForLogin ( t * testing . T ) {
t . Run ( "Given a generic OAuth provider" , func ( t * testing . T ) {
2023-12-08 11:20:42 +01:00
provider := NewGenericOAuthProvider ( & social . OAuthInfo {
Extra : map [ string ] string {
"login_attribute_path" : "login" ,
} ,
} , & setting . Cfg { } , & ssosettingstests . MockService { } , featuremgmt . WithFeatures ( ) )
2020-08-03 17:33:27 +03:00
tests := [ ] struct {
Name string
2023-08-30 08:46:47 -07:00
ResponseBody any
OAuth2Extra any
2020-08-03 17:33:27 +03:00
LoginAttributePath string
ExpectedLogin string
} {
{
2020-08-04 18:28:53 +02:00
Name : "Given a valid id_token, a valid login path, no API response, use id_token" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2020-08-03 17:33:27 +03:00
// { "login": "johndoe", "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpbiI6ImpvaG5kb2UiLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.sg4sRJCNpax_76XMgr277fdxhjjtNSWXKIOFv4_GJN8" ,
} ,
LoginAttributePath : "role" ,
ExpectedLogin : "johndoe" ,
} ,
{
2020-08-04 18:28:53 +02:00
Name : "Given a valid id_token, no login path, no API response, use id_token" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2020-08-03 17:33:27 +03:00
// { "login": "johndoe", "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpbiI6ImpvaG5kb2UiLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.sg4sRJCNpax_76XMgr277fdxhjjtNSWXKIOFv4_GJN8" ,
} ,
LoginAttributePath : "" ,
ExpectedLogin : "johndoe" ,
} ,
{
2020-08-04 18:28:53 +02:00
Name : "Given no id_token, a valid login path, a valid API response, use API response" ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2020-08-03 17:33:27 +03:00
"user_uid" : "johndoe" ,
"email" : "john.doe@example.com" ,
} ,
LoginAttributePath : "user_uid" ,
ExpectedLogin : "johndoe" ,
} ,
{
2020-08-04 18:28:53 +02:00
Name : "Given no id_token, no login path, a valid API response, use API response" ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2020-08-03 17:33:27 +03:00
"login" : "johndoe" ,
} ,
LoginAttributePath : "" ,
ExpectedLogin : "johndoe" ,
} ,
{
2020-08-04 18:28:53 +02:00
Name : "Given no id_token, a login path, a valid API response without a login, use API response" ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2020-08-03 17:33:27 +03:00
"username" : "john.doe" ,
} ,
LoginAttributePath : "login" ,
ExpectedLogin : "john.doe" ,
} ,
{
2020-08-04 18:28:53 +02:00
Name : "Given no id_token, a valid login path, no API response, no data" ,
2020-08-03 17:33:27 +03:00
LoginAttributePath : "login" ,
ExpectedLogin : "" ,
} ,
}
for _ , test := range tests {
provider . loginAttributePath = test . LoginAttributePath
t . Run ( test . Name , func ( t * testing . T ) {
2020-08-04 18:28:53 +02:00
body , err := json . Marshal ( test . ResponseBody )
2020-08-03 17:33:27 +03:00
require . NoError ( t , err )
ts := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( http . StatusOK )
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2020-08-04 18:28:53 +02:00
t . Log ( "Writing fake API response body" , "body" , test . ResponseBody )
_ , err = w . Write ( body )
2020-08-03 17:33:27 +03:00
require . NoError ( t , err )
} ) )
provider . apiUrl = ts . URL
staticToken := oauth2 . Token {
AccessToken : "" ,
TokenType : "" ,
RefreshToken : "" ,
Expiry : time . Now ( ) ,
}
token := staticToken . WithExtra ( test . OAuth2Extra )
2023-06-14 12:30:40 +00:00
actualResult , err := provider . UserInfo ( context . Background ( ) , ts . Client ( ) , token )
2020-08-03 17:33:27 +03:00
require . NoError ( t , err )
require . Equal ( t , test . ExpectedLogin , actualResult . Login )
} )
}
} )
}
2020-09-07 05:42:11 -04:00
2020-10-20 09:56:48 +03:00
func TestUserInfoSearchesForName ( t * testing . T ) {
t . Run ( "Given a generic OAuth provider" , func ( t * testing . T ) {
2023-12-08 11:20:42 +01:00
provider := NewGenericOAuthProvider ( & social . OAuthInfo {
Extra : map [ string ] string {
"name_attribute_path" : "name" ,
} ,
} , & setting . Cfg { } , & ssosettingstests . MockService { } , featuremgmt . WithFeatures ( ) )
2020-10-20 09:56:48 +03:00
tests := [ ] struct {
Name string
2023-08-30 08:46:47 -07:00
ResponseBody any
OAuth2Extra any
2020-10-20 09:56:48 +03:00
NameAttributePath string
ExpectedName string
} {
{
Name : "Given a valid id_token, a valid name path, no API response, use id_token" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2020-10-20 09:56:48 +03:00
// { "name": "John Doe", "login": "johndoe", "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpbiI6ImpvaG5kb2UiLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIiwibmFtZSI6IkpvaG4gRG9lIn0.oMsXH0mHxUSYMXh6FonZIWh8LgNIcYbKRLSO1bwnfSI" ,
} ,
NameAttributePath : "name" ,
ExpectedName : "John Doe" ,
} ,
{
Name : "Given a valid id_token, no name path, no API response, use id_token" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2020-10-20 09:56:48 +03:00
// { "name": "John Doe", "login": "johndoe", "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpbiI6ImpvaG5kb2UiLCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIiwibmFtZSI6IkpvaG4gRG9lIn0.oMsXH0mHxUSYMXh6FonZIWh8LgNIcYbKRLSO1bwnfSI" ,
} ,
NameAttributePath : "" ,
ExpectedName : "John Doe" ,
} ,
{
Name : "Given no id_token, a valid name path, a valid API response, use API response" ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2020-10-20 09:56:48 +03:00
"user_name" : "John Doe" ,
"login" : "johndoe" ,
"email" : "john.doe@example.com" ,
} ,
NameAttributePath : "user_name" ,
ExpectedName : "John Doe" ,
} ,
{
Name : "Given no id_token, no name path, a valid API response, use API response" ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2020-10-20 09:56:48 +03:00
"display_name" : "John Doe" ,
"login" : "johndoe" ,
} ,
NameAttributePath : "" ,
ExpectedName : "John Doe" ,
} ,
{
Name : "Given no id_token, a name path, a valid API response without a name, use API response" ,
2023-08-30 08:46:47 -07:00
ResponseBody : map [ string ] any {
2020-10-20 09:56:48 +03:00
"display_name" : "John Doe" ,
"username" : "john.doe" ,
} ,
NameAttributePath : "name" ,
ExpectedName : "John Doe" ,
} ,
{
Name : "Given no id_token, a valid name path, no API response, no data" ,
NameAttributePath : "name" ,
ExpectedName : "" ,
} ,
}
for _ , test := range tests {
provider . nameAttributePath = test . NameAttributePath
t . Run ( test . Name , func ( t * testing . T ) {
body , err := json . Marshal ( test . ResponseBody )
require . NoError ( t , err )
ts := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( http . StatusOK )
w . Header ( ) . Set ( "Content-Type" , "application/json" )
t . Log ( "Writing fake API response body" , "body" , test . ResponseBody )
_ , err = w . Write ( body )
require . NoError ( t , err )
} ) )
provider . apiUrl = ts . URL
staticToken := oauth2 . Token {
AccessToken : "" ,
TokenType : "" ,
RefreshToken : "" ,
Expiry : time . Now ( ) ,
}
token := staticToken . WithExtra ( test . OAuth2Extra )
2023-06-14 12:30:40 +00:00
actualResult , err := provider . UserInfo ( context . Background ( ) , ts . Client ( ) , token )
2020-10-20 09:56:48 +03:00
require . NoError ( t , err )
require . Equal ( t , test . ExpectedName , actualResult . Name )
} )
}
} )
}
2021-11-12 10:22:04 +01:00
func TestUserInfoSearchesForGroup ( t * testing . T ) {
t . Run ( "Given a generic OAuth provider" , func ( t * testing . T ) {
tests := [ ] struct {
name string
groupsAttributePath string
2023-08-30 08:46:47 -07:00
responseBody any
2021-11-12 10:22:04 +01:00
expectedResult [ ] string
} {
{
name : "If groups are not set, user groups are nil" ,
groupsAttributePath : "" ,
expectedResult : nil ,
} ,
{
name : "If groups are empty, user groups are nil" ,
groupsAttributePath : "info.groups" ,
2023-08-30 08:46:47 -07:00
responseBody : map [ string ] any {
"info" : map [ string ] any {
2021-11-12 10:22:04 +01:00
"groups" : [ ] string { } ,
} ,
} ,
expectedResult : nil ,
} ,
{
name : "If groups are set, user groups are set" ,
groupsAttributePath : "info.groups" ,
2023-08-30 08:46:47 -07:00
responseBody : map [ string ] any {
"info" : map [ string ] any {
2021-11-12 10:22:04 +01:00
"groups" : [ ] string { "foo" , "bar" } ,
} ,
} ,
expectedResult : [ ] string { "foo" , "bar" } ,
} ,
}
for _ , test := range tests {
t . Run ( test . name , func ( t * testing . T ) {
body , err := json . Marshal ( test . responseBody )
require . NoError ( t , err )
ts := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
w . WriteHeader ( http . StatusOK )
w . Header ( ) . Set ( "Content-Type" , "application/json" )
t . Log ( "Writing fake API response body" , "body" , test . responseBody )
_ , err := w . Write ( body )
require . NoError ( t , err )
} ) )
2023-11-20 09:45:40 +01:00
2023-12-08 11:20:42 +01:00
provider := NewGenericOAuthProvider ( & social . OAuthInfo {
GroupsAttributePath : test . groupsAttributePath ,
ApiUrl : ts . URL ,
} , & setting . Cfg { } , & ssosettingstests . MockService { } , featuremgmt . WithFeatures ( ) )
2023-11-20 09:45:40 +01:00
2021-11-12 10:22:04 +01:00
token := & oauth2 . Token {
AccessToken : "" ,
TokenType : "" ,
RefreshToken : "" ,
Expiry : time . Now ( ) ,
}
2023-06-14 12:30:40 +00:00
userInfo , err := provider . UserInfo ( context . Background ( ) , ts . Client ( ) , token )
2021-11-12 10:22:04 +01:00
assert . NoError ( t , err )
assert . Equal ( t , test . expectedResult , userInfo . Groups )
} )
}
} )
}
2020-09-07 05:42:11 -04:00
func TestPayloadCompression ( t * testing . T ) {
2023-12-08 11:20:42 +01:00
provider := NewGenericOAuthProvider ( & social . OAuthInfo {
EmailAttributePath : "email" ,
} , & setting . Cfg { } , & ssosettingstests . MockService { } , featuremgmt . WithFeatures ( ) )
2020-09-07 05:42:11 -04:00
tests := [ ] struct {
Name string
2023-08-30 08:46:47 -07:00
OAuth2Extra any
2020-09-07 05:42:11 -04:00
ExpectedEmail string
} {
{
Name : "Given a valid DEFLATE compressed id_token, return userInfo" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2020-09-07 05:42:11 -04:00
// { "role": "Admin", "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsInppcCI6IkRFRiJ9.eJyrVkrNTczMUbJSysrPyNNLyU91SK1IzC3ISdVLzs9V0lEqys9JBco6puRm5inVAgCFRw_6.XrV4ZKhw19dTcnviXanBD8lwjeALCYtDiESMmGzC-ho" ,
} ,
ExpectedEmail : "john.doe@example.com" ,
} ,
2022-01-25 17:09:35 +01:00
{
Name : "Given a valid DEFLATE compressed id_token with numeric header, return userInfo" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2022-01-25 17:09:35 +01:00
// Generated from https://token.dev/
"id_token" : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsInZlciI6NH0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTY0MjUxNjYwNSwiZXhwIjoxNjQyNTIwMjA1LCJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.ANndoPWIHNjKPG8na7UUq7nan1RgF8-ze8STU31RXcA" ,
} ,
ExpectedEmail : "john.doe@example.com" ,
} ,
2020-09-07 05:42:11 -04:00
{
Name : "Given an invalid DEFLATE compressed id_token, return nil" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2020-09-07 05:42:11 -04:00
// { "role": "Admin", "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsInppcCI6IkRFRiJ9.00eJyrVkrNTczMUbJSysrPyNNLyU91SK1IzC3ISdVLzs9V0lEqys9JBco6puRm5inVAgCFRw_6.XrV4ZKhw19dTcnviXanBD8lwjeALCYtDiESMmGzC-ho" ,
} ,
ExpectedEmail : "" ,
} ,
{
Name : "Given an unsupported GZIP compressed id_token, return nil" ,
2023-08-30 08:46:47 -07:00
OAuth2Extra : map [ string ] any {
2020-09-07 05:42:11 -04:00
// { "role": "Admin", "email": "john.doe@example.com" }
"id_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAKtWSs1NzMxRslLKys_I00vJT3VIrUjMLchJ1UvOz1XSUSrKz0kFyjqm5GbmKdUCANotxTkvAAAA.85AXm3JOF5qflEA0goDFvlbZl2q3eFvqVcehz860W-o" ,
} ,
ExpectedEmail : "" ,
} ,
}
for _ , test := range tests {
t . Run ( test . Name , func ( t * testing . T ) {
staticToken := oauth2 . Token {
AccessToken : "" ,
TokenType : "" ,
RefreshToken : "" ,
Expiry : time . Now ( ) ,
}
token := staticToken . WithExtra ( test . OAuth2Extra )
userInfo := provider . extractFromToken ( token )
if test . ExpectedEmail == "" {
require . Nil ( t , userInfo , "Testing case %q" , test . Name )
} else {
require . NotNil ( t , userInfo , "Testing case %q" , test . Name )
require . Equal ( t , test . ExpectedEmail , userInfo . Email )
}
} )
}
}
2023-11-20 09:45:40 +01:00
func TestSocialGenericOAuth_InitializeExtraFields ( t * testing . T ) {
type settingFields struct {
nameAttributePath string
loginAttributePath string
idTokenAttributeName string
teamIds [ ] string
allowedOrganizations [ ] string
}
testCases := [ ] struct {
name string
2023-12-08 11:20:42 +01:00
settings * social . OAuthInfo
2023-11-20 09:45:40 +01:00
want settingFields
} {
{
name : "nameAttributePath is set" ,
2023-12-08 11:20:42 +01:00
settings : & social . OAuthInfo {
Extra : map [ string ] string {
"name_attribute_path" : "name" ,
} ,
2023-11-20 09:45:40 +01:00
} ,
want : settingFields {
nameAttributePath : "name" ,
loginAttributePath : "" ,
idTokenAttributeName : "" ,
teamIds : [ ] string { } ,
allowedOrganizations : [ ] string { } ,
} ,
} ,
{
name : "loginAttributePath is set" ,
2023-12-08 11:20:42 +01:00
settings : & social . OAuthInfo {
Extra : map [ string ] string {
"login_attribute_path" : "login" ,
} ,
2023-11-20 09:45:40 +01:00
} ,
want : settingFields {
nameAttributePath : "" ,
loginAttributePath : "login" ,
idTokenAttributeName : "" ,
teamIds : [ ] string { } ,
allowedOrganizations : [ ] string { } ,
} ,
} ,
{
name : "idTokenAttributeName is set" ,
2023-12-08 11:20:42 +01:00
settings : & social . OAuthInfo {
Extra : map [ string ] string {
"id_token_attribute_name" : "id_token" ,
} ,
2023-11-20 09:45:40 +01:00
} ,
want : settingFields {
nameAttributePath : "" ,
loginAttributePath : "" ,
idTokenAttributeName : "id_token" ,
teamIds : [ ] string { } ,
allowedOrganizations : [ ] string { } ,
} ,
} ,
{
name : "teamIds is set" ,
2023-12-08 11:20:42 +01:00
settings : & social . OAuthInfo {
Extra : map [ string ] string {
"team_ids" : "[\"team1\", \"team2\"]" ,
} ,
2023-11-20 09:45:40 +01:00
} ,
want : settingFields {
nameAttributePath : "" ,
loginAttributePath : "" ,
idTokenAttributeName : "" ,
teamIds : [ ] string { "team1" , "team2" } ,
allowedOrganizations : [ ] string { } ,
} ,
} ,
{
name : "allowedOrganizations is set" ,
2023-12-08 11:20:42 +01:00
settings : & social . OAuthInfo {
Extra : map [ string ] string {
"allowed_organizations" : "org1, org2" ,
} ,
2023-11-20 09:45:40 +01:00
} ,
want : settingFields {
nameAttributePath : "" ,
loginAttributePath : "" ,
idTokenAttributeName : "" ,
teamIds : [ ] string { } ,
allowedOrganizations : [ ] string { "org1" , "org2" } ,
} ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . name , func ( t * testing . T ) {
2023-12-08 11:20:42 +01:00
s := NewGenericOAuthProvider ( tc . settings , & setting . Cfg { } , & ssosettingstests . MockService { } , featuremgmt . WithFeatures ( ) )
2023-11-20 09:45:40 +01:00
require . Equal ( t , tc . want . nameAttributePath , s . nameAttributePath )
require . Equal ( t , tc . want . loginAttributePath , s . loginAttributePath )
require . Equal ( t , tc . want . idTokenAttributeName , s . idTokenAttributeName )
require . Equal ( t , tc . want . teamIds , s . teamIds )
require . Equal ( t , tc . want . allowedOrganizations , s . allowedOrganizations )
} )
}
}