mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
OAuth: clarify role & group paths prefer id_token over userinfo api (#39066)
* OAuth: clarify role & group paths prefer id_token over userinfo api (#39066) Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Kevin Minehart <kmineh0151@gmail.com>
This commit is contained in:
parent
ae4900e76f
commit
89878dae1b
@ -55,26 +55,30 @@ You can also specify the SSL/TLS configuration used by the client.
|
||||
|
||||
Set `empty_scopes` to true to use an empty scope during authentication. By default, Grafana uses `user:email` as scope.
|
||||
|
||||
Grafana will attempt to determine the user's e-mail address by querying the OAuth provider as described below in the following order until an e-mail address is found:
|
||||
### Email address
|
||||
|
||||
Grafana determines a user's email address by querying the OAuth provider until it finds an e-mail address:
|
||||
|
||||
1. Check for the presence of an e-mail address via the `email` field encoded in the OAuth `id_token` parameter.
|
||||
1. Check for the presence of an e-mail address using the [JMESPath](http://jmespath.org/examples.html) specified via the `email_attribute_path` configuration option. The JSON used for the path lookup is the HTTP response obtained from querying the UserInfo endpoint specified via the `api_url` configuration option.
|
||||
**Note**: Only available in Grafana v6.4+.
|
||||
1. Check for the presence of an e-mail address in the `attributes` map encoded in the OAuth `id_token` parameter. By default Grafana will perform a lookup into the attributes map using the `email:primary` key, however, this is configurable and can be adjusted by using the `email_attribute_name` configuration option.
|
||||
1. Query the `/emails` endpoint of the OAuth provider's API (configured with `api_url`) and check for the presence of an e-mail address marked as a primary address.
|
||||
1. If no e-mail address is found in steps (1-4), then the e-mail address of the user is set to the empty string.
|
||||
1. Query the `/emails` endpoint of the OAuth provider's API (configured with `api_url`), then check for the presence of an email address marked as a primary address.
|
||||
1. If no email address is found in steps (1-4), then the email address of the user is set to an empty string.
|
||||
|
||||
Grafana will also attempt to do role mapping through OAuth as described below.
|
||||
### Roles
|
||||
|
||||
Check for the presence of a role using the [JMESPath](http://jmespath.org/examples.html) specified via the `role_attribute_path` configuration option. The JSON used for the path lookup is the HTTP response obtained from querying the UserInfo endpoint specified via the `api_url` configuration option. The result after evaluating the `role_attribute_path` JMESPath expression needs to be a valid Grafana role, i.e. `Viewer`, `Editor` or `Admin`.
|
||||
Grafana checks for the presence of a role using the [JMESPath](http://jmespath.org/examples.html) specified via the `role_attribute_path` configuration option. The JMESPath is applied to the `id_token` first. If there is no match, then the UserInfo endpoint specified via the `api_url` configuration option is tried next. The result after evaluation of the `role_attribute_path` JMESPath expression should be a valid Grafana role, for example, `Viewer`, `Editor` or `Admin`.
|
||||
|
||||
Grafana also attempts to map teams through OAuth as described below.
|
||||
For more information, refer to the [JMESPath examples](#jmespath-examples).
|
||||
|
||||
Check for the presence of groups using the [JMESPath](http://jmespath.org/examples.html) specified via the `groups_attribute_path` configuration option. The JSON used for the path lookup is the HTTP response obtained from querying the UserInfo endpoint specified via the `api_url` configuration option. After evaluating the `groups_attribute_path` JMESPath expression, the result should be a string array of groups.
|
||||
### Groups / Teams
|
||||
|
||||
Similarly, group mappings are made using [JMESPath](http://jmespath.org/examples.html) with the `groups_attribute_path` configuration option. The `id_token` is attempted first, followed by the UserInfo from the `api_url`. The result of the JMESPath expression should be a string array of groups.
|
||||
|
||||
Furthermore, Grafana will check for the presence of at least one of the teams specified via the `team_ids` configuration option using the [JMESPath](http://jmespath.org/examples.html) specified via the `team_ids_attribute_path` configuration option. The JSON used for the path lookup is the HTTP response obtained from querying the Teams endpoint specified via the `teams_url` configuration option (using `/teams` as a fallback endpoint). The result should be a string array of Grafana Team IDs. Using this setting ensures that only certain teams is allowed to authenticate to Grafana using your OAuth provider.
|
||||
|
||||
See [JMESPath examples](#jmespath-examples) for more information.
|
||||
### Login
|
||||
|
||||
Customize user login using `login_attribute_path` configuration option. Order of operations is as follows:
|
||||
|
||||
|
@ -149,19 +149,21 @@ func (s *SocialGenericOAuth) UserInfo(client *http.Client, token *oauth2.Token)
|
||||
if userInfo.Role == "" {
|
||||
role, err := s.extractRole(data)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to extract role", "error", err)
|
||||
s.log.Warn("Failed to extract role", "error", err)
|
||||
} else if role != "" {
|
||||
s.log.Debug("Setting user info role from extracted role")
|
||||
userInfo.Role = role
|
||||
}
|
||||
}
|
||||
|
||||
groups, err := s.extractGroups(data)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to extract groups", "error", err)
|
||||
} else if len(groups) > 0 {
|
||||
s.log.Debug("Setting user info groups from extracted groups")
|
||||
userInfo.Groups = groups
|
||||
if userInfo.Groups != nil && len(userInfo.Groups) == 0 {
|
||||
groups, err := s.extractGroups(data)
|
||||
if err != nil {
|
||||
s.log.Warn("Failed to extract groups", "err", err)
|
||||
} else if len(groups) > 0 {
|
||||
s.log.Debug("Setting user info groups from extracted groups")
|
||||
userInfo.Groups = groups
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -379,6 +379,48 @@ func TestUserInfoSearchesForEmailAndRole(t *testing.T) {
|
||||
ExpectedEmail: "john.doe@example.com",
|
||||
ExpectedRole: "FromResponse",
|
||||
},
|
||||
{
|
||||
Name: "Given a valid id_token, a valid advanced JMESPath role path, derive the role",
|
||||
OAuth2Extra: map[string]interface{}{
|
||||
// { "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)",
|
||||
OAuth2Extra: map[string]interface{}{
|
||||
// { "email": "john.doe@example.com" }
|
||||
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIn0.k5GwPcZvGe2BE_jgwN0ntz0nz4KlYhEd0hRRLApkTJ4",
|
||||
},
|
||||
ResponseBody: map[string]interface{}{
|
||||
"info": map[string]interface{}{
|
||||
"roles": []string{"engineering", "SRE"},
|
||||
},
|
||||
},
|
||||
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",
|
||||
OAuth2Extra: map[string]interface{}{
|
||||
// { "email": "john.doe@example.com",
|
||||
// "info": { "roles": [ "dev", "engineering" ] }}
|
||||
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImpvaG4uZG9lQGV4YW1wbGUuY29tIiwiaW5mbyI6eyJyb2xlcyI6WyJkZXYiLCJlbmdpbmVlcmluZyJdfX0.RmmQfv25eXb4p3wMrJsvXfGQ6EXhGtwRXo6SlCFHRNg",
|
||||
},
|
||||
ResponseBody: map[string]interface{}{
|
||||
"info": map[string]interface{}{
|
||||
"roles": []string{"engineering", "SRE"},
|
||||
},
|
||||
},
|
||||
RoleAttributePath: "contains(info.roles[*], 'SRE') && 'Admin' || contains(info.roles[*], 'dev') && 'Editor' || 'Viewer'",
|
||||
ExpectedEmail: "john.doe@example.com",
|
||||
ExpectedRole: "Editor",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
Loading…
Reference in New Issue
Block a user