mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
OAuth: Make generic teams URL and JMES path configurable (#37233)
OAuth: Make generic teams URL and JMES path configurable
This commit is contained in:
@@ -498,9 +498,11 @@ role_attribute_path =
|
||||
role_attribute_strict = false
|
||||
groups_attribute_path =
|
||||
id_token_attribute_name =
|
||||
team_ids_attribute_path =
|
||||
auth_url =
|
||||
token_url =
|
||||
api_url =
|
||||
teams_url =
|
||||
allowed_domains =
|
||||
team_ids =
|
||||
allowed_organizations =
|
||||
|
@@ -483,12 +483,14 @@
|
||||
;auth_url = https://foo.bar/login/oauth/authorize
|
||||
;token_url = https://foo.bar/login/oauth/access_token
|
||||
;api_url = https://foo.bar/user
|
||||
;teams_url =
|
||||
;allowed_domains =
|
||||
;team_ids =
|
||||
;allowed_organizations =
|
||||
;role_attribute_path =
|
||||
;role_attribute_strict = false
|
||||
;groups_attribute_path =
|
||||
;team_ids_attribute_path =
|
||||
;tls_skip_verify_insecure = false
|
||||
;tls_client_cert =
|
||||
;tls_client_key =
|
||||
|
@@ -72,6 +72,8 @@ Grafana also attempts to map teams through OAuth as described below.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
Customize user login using `login_attribute_path` configuration option. Order of operations is as follows:
|
||||
@@ -126,6 +128,8 @@ scopes = account email
|
||||
auth_url = https://bitbucket.org/site/oauth2/authorize
|
||||
token_url = https://bitbucket.org/site/oauth2/access_token
|
||||
api_url = https://api.bitbucket.org/2.0/user
|
||||
teams_url = https://api.bitbucket.org/2.0/user/permissions/workspaces
|
||||
team_ids_attribute_path = values[*].workspace.slug
|
||||
team_ids =
|
||||
allowed_organizations =
|
||||
```
|
||||
|
@@ -11,6 +11,7 @@ import (
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
@@ -21,6 +22,7 @@ type SocialGenericOAuth struct {
|
||||
*SocialBase
|
||||
allowedOrganizations []string
|
||||
apiUrl string
|
||||
teamsUrl string
|
||||
emailAttributeName string
|
||||
emailAttributePath string
|
||||
loginAttributePath string
|
||||
@@ -29,7 +31,8 @@ type SocialGenericOAuth struct {
|
||||
roleAttributeStrict bool
|
||||
groupsAttributePath string
|
||||
idTokenAttributeName string
|
||||
teamIds []int
|
||||
teamIdsAttributePath string
|
||||
teamIds []string
|
||||
}
|
||||
|
||||
func (s *SocialGenericOAuth) Type() int {
|
||||
@@ -41,8 +44,8 @@ func (s *SocialGenericOAuth) IsTeamMember(client *http.Client) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
teamMemberships, ok := s.FetchTeamMemberships(client)
|
||||
if !ok {
|
||||
teamMemberships, err := s.FetchTeamMemberships(client)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -412,15 +415,36 @@ func (s *SocialGenericOAuth) FetchPrivateEmail(client *http.Client) (string, err
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (s *SocialGenericOAuth) FetchTeamMemberships(client *http.Client) ([]int, bool) {
|
||||
func (s *SocialGenericOAuth) FetchTeamMemberships(client *http.Client) ([]string, error) {
|
||||
var err error
|
||||
var ids []string
|
||||
|
||||
if s.teamsUrl == "" {
|
||||
ids, err = s.fetchTeamMembershipsFromDeprecatedTeamsUrl(client)
|
||||
} else {
|
||||
ids, err = s.fetchTeamMembershipsFromTeamsUrl(client)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
s.log.Debug("Received team memberships", "ids", ids)
|
||||
}
|
||||
|
||||
return ids, err
|
||||
}
|
||||
|
||||
func (s *SocialGenericOAuth) fetchTeamMembershipsFromDeprecatedTeamsUrl(client *http.Client) ([]string, error) {
|
||||
var response httpGetResponse
|
||||
var err error
|
||||
var ids []string
|
||||
|
||||
type Record struct {
|
||||
Id int `json:"id"`
|
||||
}
|
||||
|
||||
response, err := s.httpGet(client, fmt.Sprintf(s.apiUrl+"/teams"))
|
||||
response, err = s.httpGet(client, fmt.Sprintf(s.apiUrl+"/teams"))
|
||||
if err != nil {
|
||||
s.log.Error("Error getting team memberships", "url", s.apiUrl+"/teams", "error", err)
|
||||
return nil, false
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
var records []Record
|
||||
@@ -428,17 +452,32 @@ func (s *SocialGenericOAuth) FetchTeamMemberships(client *http.Client) ([]int, b
|
||||
err = json.Unmarshal(response.Body, &records)
|
||||
if err != nil {
|
||||
s.log.Error("Error decoding team memberships response", "raw_json", string(response.Body), "error", err)
|
||||
return nil, false
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
var ids = make([]int, len(records))
|
||||
ids = make([]string, len(records))
|
||||
for i, record := range records {
|
||||
ids[i] = record.Id
|
||||
ids[i] = strconv.Itoa(record.Id)
|
||||
}
|
||||
|
||||
s.log.Debug("Received team memberships", "ids", ids)
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
return ids, true
|
||||
func (s *SocialGenericOAuth) fetchTeamMembershipsFromTeamsUrl(client *http.Client) ([]string, error) {
|
||||
if s.teamIdsAttributePath == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
var response httpGetResponse
|
||||
var err error
|
||||
|
||||
response, err = s.httpGet(client, fmt.Sprintf(s.teamsUrl))
|
||||
if err != nil {
|
||||
s.log.Error("Error getting team memberships", "url", s.teamsUrl, "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.searchJSONForStringArrayAttr(s.teamIdsAttributePath, response.Body)
|
||||
}
|
||||
|
||||
func (s *SocialGenericOAuth) FetchOrganizations(client *http.Client) ([]string, bool) {
|
||||
|
@@ -38,9 +38,11 @@ type OAuthInfo struct {
|
||||
RoleAttributePath string
|
||||
RoleAttributeStrict bool
|
||||
GroupsAttributePath string
|
||||
TeamIdsAttributePath string
|
||||
AllowedDomains []string
|
||||
HostedDomain string
|
||||
ApiUrl string
|
||||
TeamsUrl string
|
||||
AllowSignup bool
|
||||
Name string
|
||||
TlsClientCert string
|
||||
@@ -60,26 +62,28 @@ func ProvideService(cfg *setting.Cfg) *SocialService {
|
||||
sec := cfg.Raw.Section("auth." + name)
|
||||
|
||||
info := &OAuthInfo{
|
||||
ClientId: sec.Key("client_id").String(),
|
||||
ClientSecret: sec.Key("client_secret").String(),
|
||||
Scopes: util.SplitString(sec.Key("scopes").String()),
|
||||
AuthUrl: sec.Key("auth_url").String(),
|
||||
TokenUrl: sec.Key("token_url").String(),
|
||||
ApiUrl: sec.Key("api_url").String(),
|
||||
Enabled: sec.Key("enabled").MustBool(),
|
||||
EmailAttributeName: sec.Key("email_attribute_name").String(),
|
||||
EmailAttributePath: sec.Key("email_attribute_path").String(),
|
||||
RoleAttributePath: sec.Key("role_attribute_path").String(),
|
||||
RoleAttributeStrict: sec.Key("role_attribute_strict").MustBool(),
|
||||
GroupsAttributePath: sec.Key("groups_attribute_path").String(),
|
||||
AllowedDomains: util.SplitString(sec.Key("allowed_domains").String()),
|
||||
HostedDomain: sec.Key("hosted_domain").String(),
|
||||
AllowSignup: sec.Key("allow_sign_up").MustBool(),
|
||||
Name: sec.Key("name").MustString(name),
|
||||
TlsClientCert: sec.Key("tls_client_cert").String(),
|
||||
TlsClientKey: sec.Key("tls_client_key").String(),
|
||||
TlsClientCa: sec.Key("tls_client_ca").String(),
|
||||
TlsSkipVerify: sec.Key("tls_skip_verify_insecure").MustBool(),
|
||||
ClientId: sec.Key("client_id").String(),
|
||||
ClientSecret: sec.Key("client_secret").String(),
|
||||
Scopes: util.SplitString(sec.Key("scopes").String()),
|
||||
AuthUrl: sec.Key("auth_url").String(),
|
||||
TokenUrl: sec.Key("token_url").String(),
|
||||
ApiUrl: sec.Key("api_url").String(),
|
||||
TeamsUrl: sec.Key("teams_url").String(),
|
||||
Enabled: sec.Key("enabled").MustBool(),
|
||||
EmailAttributeName: sec.Key("email_attribute_name").String(),
|
||||
EmailAttributePath: sec.Key("email_attribute_path").String(),
|
||||
RoleAttributePath: sec.Key("role_attribute_path").String(),
|
||||
RoleAttributeStrict: sec.Key("role_attribute_strict").MustBool(),
|
||||
GroupsAttributePath: sec.Key("groups_attribute_path").String(),
|
||||
TeamIdsAttributePath: sec.Key("team_ids_attribute_path").String(),
|
||||
AllowedDomains: util.SplitString(sec.Key("allowed_domains").String()),
|
||||
HostedDomain: sec.Key("hosted_domain").String(),
|
||||
AllowSignup: sec.Key("allow_sign_up").MustBool(),
|
||||
Name: sec.Key("name").MustString(name),
|
||||
TlsClientCert: sec.Key("tls_client_cert").String(),
|
||||
TlsClientKey: sec.Key("tls_client_key").String(),
|
||||
TlsClientCa: sec.Key("tls_client_ca").String(),
|
||||
TlsSkipVerify: sec.Key("tls_skip_verify_insecure").MustBool(),
|
||||
}
|
||||
|
||||
// when empty_scopes parameter exists and is true, overwrite scope with empty value
|
||||
@@ -162,6 +166,7 @@ func ProvideService(cfg *setting.Cfg) *SocialService {
|
||||
ss.socialMap["generic_oauth"] = &SocialGenericOAuth{
|
||||
SocialBase: newSocialBase(name, &config, info),
|
||||
apiUrl: info.ApiUrl,
|
||||
teamsUrl: info.TeamsUrl,
|
||||
emailAttributeName: info.EmailAttributeName,
|
||||
emailAttributePath: info.EmailAttributePath,
|
||||
nameAttributePath: sec.Key("name_attribute_path").String(),
|
||||
@@ -170,7 +175,8 @@ func ProvideService(cfg *setting.Cfg) *SocialService {
|
||||
groupsAttributePath: info.GroupsAttributePath,
|
||||
loginAttributePath: sec.Key("login_attribute_path").String(),
|
||||
idTokenAttributeName: sec.Key("id_token_attribute_name").String(),
|
||||
teamIds: sec.Key("team_ids").Ints(","),
|
||||
teamIdsAttributePath: sec.Key("team_ids_attribute_path").String(),
|
||||
teamIds: sec.Key("team_ids").Strings(","),
|
||||
allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()),
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user