mirror of https://github.com/grafana/grafana.git
				
				
				
			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. | 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 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. | 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+. |    **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. 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. 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 e-mail address is found in steps (1-4), then the e-mail address of the user is set to the empty string. | 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. | 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: | 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 == "" { | 		if userInfo.Role == "" { | ||||||
| 			role, err := s.extractRole(data) | 			role, err := s.extractRole(data) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				s.log.Error("Failed to extract role", "error", err) | 				s.log.Warn("Failed to extract role", "error", err) | ||||||
| 			} else if role != "" { | 			} else if role != "" { | ||||||
| 				s.log.Debug("Setting user info role from extracted role") | 				s.log.Debug("Setting user info role from extracted role") | ||||||
| 				userInfo.Role = role | 				userInfo.Role = role | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		groups, err := s.extractGroups(data) | 		if userInfo.Groups != nil && len(userInfo.Groups) == 0 { | ||||||
| 		if err != nil { | 			groups, err := s.extractGroups(data) | ||||||
| 			s.log.Error("Failed to extract groups", "error", err) | 			if err != nil { | ||||||
| 		} else if len(groups) > 0 { | 				s.log.Warn("Failed to extract groups", "err", err) | ||||||
| 			s.log.Debug("Setting user info groups from extracted groups") | 			} else if len(groups) > 0 { | ||||||
| 			userInfo.Groups = groups | 				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", | 				ExpectedEmail:     "john.doe@example.com", | ||||||
| 				ExpectedRole:      "FromResponse", | 				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 { | 		for _, test := range tests { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue