mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
OAuth: Allow role mapping from GitHub and GitLab groups (#52407)
* OAuth: Add extract role support to github OAuth: correct github errors Oauth: add github tests Oauth: Allow mapping via group memberships Oauth: Add markdown instructions to the new mappers fix lint * Apply suggestions from code review Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> Co-authored-by: Vardan Torosyan <vardants@gmail.com> * Apply suggestions from code review Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> Co-authored-by: Vardan Torosyan <vardants@gmail.com>
This commit is contained in:
parent
01d561224c
commit
5c4aa4a7ac
@ -467,6 +467,8 @@ api_url = https://api.github.com/user
|
|||||||
allowed_domains =
|
allowed_domains =
|
||||||
team_ids =
|
team_ids =
|
||||||
allowed_organizations =
|
allowed_organizations =
|
||||||
|
role_attribute_path =
|
||||||
|
role_attribute_strict = false
|
||||||
|
|
||||||
#################################### GitLab Auth #########################
|
#################################### GitLab Auth #########################
|
||||||
[auth.gitlab]
|
[auth.gitlab]
|
||||||
|
@ -101,6 +101,35 @@ allow_sign_up = true
|
|||||||
allowed_organizations = github google
|
allowed_organizations = github google
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Map roles
|
||||||
|
|
||||||
|
You can use GitHub OAuth to map roles. During mapping, Grafana checks for the presence of a role using the [JMESPath](http://jmespath.org/examples.html) specified via the `role_attribute_path` configuration option.
|
||||||
|
|
||||||
|
For the path lookup, Grafana uses JSON obtained from querying GitHub's API [`/api/user`](https://docs.github.com/en/rest/users/users#get-the-authenticated-user=) endpoint and a `groups` key containing all of the user's teams (retrieved from `/api/user/teams`).
|
||||||
|
|
||||||
|
The result of evaluating the `role_attribute_path` JMESPath expression must be a valid Grafana role, for example, `Viewer`, `Editor` or `Admin`. For more information about roles and permissions in Grafana, refer to [Roles and permissions]({{< relref "../../../administration/roles-and-permissions/" >}}).
|
||||||
|
|
||||||
|
An example Query could look like the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
role_attribute_path = [login==octocat] && 'Admin' || 'Viewer'
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows the user with login "octocat" to be mapped to the `Admin` role,
|
||||||
|
but all other users to be mapped to the `Viewer` role.
|
||||||
|
|
||||||
|
#### Map roles using teams
|
||||||
|
|
||||||
|
Teams can also be used to map roles. For instance,
|
||||||
|
if you have a team called 'example-group' you can use the following snippet to
|
||||||
|
ensure those members inherit the role 'Editor'.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
role_attribute_path = contains(groups[*], '@github/example-group') && 'Editor' || 'Viewer'
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: If a match is found in other fields, teams will be ignored.
|
||||||
|
|
||||||
### Team Sync (Enterprise only)
|
### Team Sync (Enterprise only)
|
||||||
|
|
||||||
> Only available in Grafana Enterprise v6.3+
|
> Only available in Grafana Enterprise v6.3+
|
||||||
|
@ -122,7 +122,7 @@ role_attribute_path = is_admin && 'Admin' || 'Viewer'
|
|||||||
|
|
||||||
You can use GitLab OAuth to map roles. During mapping, Grafana checks for the presence of a role using the [JMESPath](http://jmespath.org/examples.html) specified via the `role_attribute_path` configuration option.
|
You can use GitLab OAuth to map roles. During mapping, Grafana checks for the presence of a role using the [JMESPath](http://jmespath.org/examples.html) specified via the `role_attribute_path` configuration option.
|
||||||
|
|
||||||
For the path lookup, Grafana uses JSON obtained from querying GitLab's API [`/api/v4/user`](https://docs.gitlab.com/ee/api/users.html#list-current-user-for-normal-users) endpoint. The result of evaluating the `role_attribute_path` JMESPath expression must be a valid Grafana role, for example, `Viewer`, `Editor` or `Admin`. For more information about roles and permissions in Grafana, refer to [Roles and permissions]({{< relref "../../../administration/roles-and-permissions/" >}}).
|
For the path lookup, Grafana uses JSON obtained from querying GitLab's API [`/api/v4/user`](https://docs.gitlab.com/ee/api/users.html#list-current-user-for-normal-users) endpoint and a `groups` key containing all of the user's teams. The result of evaluating the `role_attribute_path` JMESPath expression must be a valid Grafana role, for example, `Viewer`, `Editor` or `Admin`. For more information about roles and permissions in Grafana, refer to [Roles and permissions]({{< relref "../../../administration/roles-and-permissions/" >}}).
|
||||||
|
|
||||||
An example Query could look like the following:
|
An example Query could look like the following:
|
||||||
|
|
||||||
@ -132,6 +132,19 @@ role_attribute_path = is_admin && 'Admin' || 'Viewer'
|
|||||||
|
|
||||||
This allows every GitLab Admin to be an Admin in Grafana.
|
This allows every GitLab Admin to be an Admin in Grafana.
|
||||||
|
|
||||||
|
#### Map roles using groups
|
||||||
|
|
||||||
|
Groups can also be used to map roles. Group name (lowercased and unique) is used instead of display name for identifying groups
|
||||||
|
|
||||||
|
For instance, if you have a group with display name 'Example-Group' you can use the following snippet to
|
||||||
|
ensure those members inherit the role 'Editor'.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
role_attribute_path = contains(groups[*], 'example-group') && 'Editor' || 'Viewer'
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: If a match is found in other fields, groups will be ignored.
|
||||||
|
|
||||||
### Team Sync (Enterprise only)
|
### Team Sync (Enterprise only)
|
||||||
|
|
||||||
> Only available in Grafana Enterprise v6.4+
|
> Only available in Grafana Enterprise v6.4+
|
||||||
|
@ -258,7 +258,7 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Fetch groups when ClaimsNames and ClaimsSources is set",
|
name: "Fetch groups when ClaimsNames and ClaimsSources is set",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
SocialBase: newSocialBase("azuread", &oauth2.Config{}, &OAuthInfo{}),
|
SocialBase: newSocialBase("azuread", &oauth2.Config{}, &OAuthInfo{}, ""),
|
||||||
},
|
},
|
||||||
claims: &azureClaims{
|
claims: &azureClaims{
|
||||||
ID: "1",
|
ID: "1",
|
||||||
|
@ -190,23 +190,31 @@ func (s *SocialGithub) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
|
|||||||
return nil, fmt.Errorf("error getting user info: %s", err)
|
return nil, fmt.Errorf("error getting user info: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(response.Body, &data)
|
if err = json.Unmarshal(response.Body, &data); err != nil {
|
||||||
if err != nil {
|
return nil, fmt.Errorf("error unmarshalling user info: %s", err)
|
||||||
return nil, fmt.Errorf("Error getting user info: %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
teamMemberships, err := s.FetchTeamMemberships(client)
|
teamMemberships, err := s.FetchTeamMemberships(client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error getting user teams: %s", err)
|
return nil, fmt.Errorf("error getting user teams: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
teams := convertToGroupList(teamMemberships)
|
teams := convertToGroupList(teamMemberships)
|
||||||
|
|
||||||
|
role, err := s.extractRole(response.Body, teams)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error("Failed to extract role", "error", err)
|
||||||
|
}
|
||||||
|
if s.roleAttributeStrict && !role.IsValid() {
|
||||||
|
return nil, errors.New("invalid role")
|
||||||
|
}
|
||||||
|
|
||||||
userInfo := &BasicUserInfo{
|
userInfo := &BasicUserInfo{
|
||||||
Name: data.Login,
|
Name: data.Login,
|
||||||
Login: data.Login,
|
Login: data.Login,
|
||||||
Id: fmt.Sprintf("%d", data.Id),
|
Id: fmt.Sprintf("%d", data.Id),
|
||||||
Email: data.Email,
|
Email: data.Email,
|
||||||
|
Role: string(role),
|
||||||
Groups: teams,
|
Groups: teams,
|
||||||
}
|
}
|
||||||
if data.Name != "" {
|
if data.Name != "" {
|
||||||
|
229
pkg/login/social/github_oauth_test.go
Normal file
229
pkg/login/social/github_oauth_test.go
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
package social
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const testGHUserTeamsJSON = `[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"node_id": "MDQ6VGVhbTE=",
|
||||||
|
"url": "https://api.github.com/teams/1",
|
||||||
|
"html_url": "https://github.com/orgs/github/teams/justice-league",
|
||||||
|
"name": "Justice League",
|
||||||
|
"slug": "justice-league",
|
||||||
|
"description": "A great team.",
|
||||||
|
"privacy": "closed",
|
||||||
|
"permission": "admin",
|
||||||
|
"members_url": "https://api.github.com/teams/1/members{/member}",
|
||||||
|
"repositories_url": "https://api.github.com/teams/1/repos",
|
||||||
|
"parent": null,
|
||||||
|
"members_count": 3,
|
||||||
|
"repos_count": 10,
|
||||||
|
"created_at": "2017-07-14T16:53:42Z",
|
||||||
|
"updated_at": "2017-08-17T12:37:15Z",
|
||||||
|
"organization": {
|
||||||
|
"login": "github",
|
||||||
|
"id": 1,
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjE=",
|
||||||
|
"url": "https://api.github.com/orgs/github",
|
||||||
|
"repos_url": "https://api.github.com/orgs/github/repos",
|
||||||
|
"events_url": "https://api.github.com/orgs/github/events",
|
||||||
|
"hooks_url": "https://api.github.com/orgs/github/hooks",
|
||||||
|
"issues_url": "https://api.github.com/orgs/github/issues",
|
||||||
|
"members_url": "https://api.github.com/orgs/github/members{/member}",
|
||||||
|
"public_members_url": "https://api.github.com/orgs/github/public_members{/member}",
|
||||||
|
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||||
|
"description": "A great organization",
|
||||||
|
"name": "github",
|
||||||
|
"company": "GitHub",
|
||||||
|
"blog": "https://github.com/blog",
|
||||||
|
"location": "San Francisco",
|
||||||
|
"email": "octocat@github.com",
|
||||||
|
"is_verified": true,
|
||||||
|
"has_organization_projects": true,
|
||||||
|
"has_repository_projects": true,
|
||||||
|
"public_repos": 2,
|
||||||
|
"public_gists": 1,
|
||||||
|
"followers": 20,
|
||||||
|
"following": 0,
|
||||||
|
"html_url": "https://github.com/octocat",
|
||||||
|
"created_at": "2008-01-14T04:33:35Z",
|
||||||
|
"updated_at": "2017-08-17T12:37:15Z",
|
||||||
|
"type": "Organization"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]`
|
||||||
|
|
||||||
|
const testGHUserJSON = `{
|
||||||
|
"login": "octocat",
|
||||||
|
"id": 1,
|
||||||
|
"node_id": "MDQ6VXNlcjE=",
|
||||||
|
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/octocat",
|
||||||
|
"html_url": "https://github.com/octocat",
|
||||||
|
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||||
|
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||||
|
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false,
|
||||||
|
"name": "monalisa octocat",
|
||||||
|
"company": "GitHub",
|
||||||
|
"blog": "https://github.com/blog",
|
||||||
|
"location": "San Francisco",
|
||||||
|
"email": "octocat@github.com",
|
||||||
|
"hireable": false,
|
||||||
|
"bio": "There once was...",
|
||||||
|
"twitter_username": "monatheoctocat",
|
||||||
|
"public_repos": 2,
|
||||||
|
"public_gists": 1,
|
||||||
|
"followers": 20,
|
||||||
|
"following": 0,
|
||||||
|
"created_at": "2008-01-14T04:33:35Z",
|
||||||
|
"updated_at": "2008-01-14T04:33:35Z",
|
||||||
|
"private_gists": 81,
|
||||||
|
"total_private_repos": 100,
|
||||||
|
"owned_private_repos": 100,
|
||||||
|
"disk_usage": 10000,
|
||||||
|
"collaborators": 8,
|
||||||
|
"two_factor_authentication": true,
|
||||||
|
"plan": {
|
||||||
|
"name": "Medium",
|
||||||
|
"space": 400,
|
||||||
|
"private_repos": 20,
|
||||||
|
"collaborators": 0
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
func TestSocialGitHub_UserInfo(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
userRawJSON string
|
||||||
|
userTeamsRawJSON string
|
||||||
|
settingAutoAssignOrgRole string
|
||||||
|
roleAttributePath string
|
||||||
|
autoAssignOrgRole string
|
||||||
|
want *BasicUserInfo
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Basic User info",
|
||||||
|
userRawJSON: testGHUserJSON,
|
||||||
|
userTeamsRawJSON: testGHUserTeamsJSON,
|
||||||
|
autoAssignOrgRole: "",
|
||||||
|
roleAttributePath: "",
|
||||||
|
want: &BasicUserInfo{
|
||||||
|
Id: "1",
|
||||||
|
Name: "monalisa octocat",
|
||||||
|
Email: "octocat@github.com",
|
||||||
|
Login: "octocat",
|
||||||
|
Company: "",
|
||||||
|
Role: "",
|
||||||
|
Groups: []string{"https://github.com/orgs/github/teams/justice-league", "@github/justice-league"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Admin mapping takes precedence over auto assign org role",
|
||||||
|
roleAttributePath: "[login==octocat] && 'Admin' || 'Viewer'",
|
||||||
|
userRawJSON: testGHUserJSON,
|
||||||
|
autoAssignOrgRole: "Editor",
|
||||||
|
userTeamsRawJSON: testGHUserTeamsJSON,
|
||||||
|
want: &BasicUserInfo{
|
||||||
|
Id: "1",
|
||||||
|
Name: "monalisa octocat",
|
||||||
|
Email: "octocat@github.com",
|
||||||
|
Login: "octocat",
|
||||||
|
Company: "",
|
||||||
|
Role: "Admin",
|
||||||
|
Groups: []string{"https://github.com/orgs/github/teams/justice-league", "@github/justice-league"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Editor mapping via groups",
|
||||||
|
roleAttributePath: "contains(groups[*], '@github/justice-league') && 'Editor' || 'Viewer'",
|
||||||
|
userRawJSON: testGHUserJSON,
|
||||||
|
autoAssignOrgRole: "Editor",
|
||||||
|
userTeamsRawJSON: testGHUserTeamsJSON,
|
||||||
|
want: &BasicUserInfo{
|
||||||
|
Id: "1",
|
||||||
|
Name: "monalisa octocat",
|
||||||
|
Email: "octocat@github.com",
|
||||||
|
Login: "octocat",
|
||||||
|
Company: "",
|
||||||
|
Role: "Editor",
|
||||||
|
Groups: []string{"https://github.com/orgs/github/teams/justice-league", "@github/justice-league"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "auto assign org role",
|
||||||
|
roleAttributePath: "",
|
||||||
|
userRawJSON: testGHUserJSON,
|
||||||
|
autoAssignOrgRole: "Editor",
|
||||||
|
userTeamsRawJSON: testGHUserTeamsJSON,
|
||||||
|
want: &BasicUserInfo{
|
||||||
|
Id: "1",
|
||||||
|
Name: "monalisa octocat",
|
||||||
|
Email: "octocat@github.com",
|
||||||
|
Login: "octocat",
|
||||||
|
Company: "",
|
||||||
|
Role: "Editor",
|
||||||
|
Groups: []string{"https://github.com/orgs/github/teams/justice-league", "@github/justice-league"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
writer.WriteHeader(http.StatusOK)
|
||||||
|
// return JSON if matches user endpoint
|
||||||
|
if strings.HasSuffix(request.URL.String(), "/user") {
|
||||||
|
writer.Header().Set("Content-Type", "application/json")
|
||||||
|
_, err := writer.Write([]byte(tt.userRawJSON))
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else if strings.HasSuffix(request.URL.String(), "/user/teams?per_page=100") {
|
||||||
|
writer.Header().Set("Content-Type", "application/json")
|
||||||
|
_, err := writer.Write([]byte(tt.userTeamsRawJSON))
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
writer.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
s := &SocialGithub{
|
||||||
|
SocialBase: newSocialBase("github", &oauth2.Config{},
|
||||||
|
&OAuthInfo{RoleAttributePath: tt.roleAttributePath}, tt.autoAssignOrgRole),
|
||||||
|
allowedOrganizations: []string{},
|
||||||
|
apiUrl: server.URL + "/user",
|
||||||
|
teamIds: []int{},
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: "fake_token",
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := s.UserInfo(server.Client(), token)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("UserInfo() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("UserInfo() got = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -16,8 +16,6 @@ type SocialGitlab struct {
|
|||||||
*SocialBase
|
*SocialBase
|
||||||
allowedGroups []string
|
allowedGroups []string
|
||||||
apiUrl string
|
apiUrl string
|
||||||
roleAttributePath string
|
|
||||||
roleAttributeStrict bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SocialGitlab) Type() int {
|
func (s *SocialGitlab) Type() int {
|
||||||
@ -106,8 +104,7 @@ func (s *SocialGitlab) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
|
|||||||
return nil, fmt.Errorf("Error getting user info: %s", err)
|
return nil, fmt.Errorf("Error getting user info: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(response.Body, &data)
|
if err = json.Unmarshal(response.Body, &data); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error getting user info: %s", err)
|
return nil, fmt.Errorf("error getting user info: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,11 +114,11 @@ func (s *SocialGitlab) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
|
|||||||
|
|
||||||
groups := s.GetGroups(client)
|
groups := s.GetGroups(client)
|
||||||
|
|
||||||
role, err := s.extractRole(response.Body)
|
role, err := s.extractRole(response.Body, groups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error("Failed to extract role", "error", err)
|
s.log.Error("Failed to extract role", "error", err)
|
||||||
}
|
}
|
||||||
if s.roleAttributeStrict && !models.RoleType(role).IsValid() {
|
if s.roleAttributeStrict && !role.IsValid() {
|
||||||
return nil, errors.New("invalid role")
|
return nil, errors.New("invalid role")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +128,7 @@ func (s *SocialGitlab) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
|
|||||||
Login: data.Username,
|
Login: data.Username,
|
||||||
Email: data.Email,
|
Email: data.Email,
|
||||||
Groups: groups,
|
Groups: groups,
|
||||||
Role: role,
|
Role: string(role),
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.IsGroupMember(groups) {
|
if !s.IsGroupMember(groups) {
|
||||||
@ -140,16 +137,3 @@ func (s *SocialGitlab) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
|
|||||||
|
|
||||||
return userInfo, nil
|
return userInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SocialGitlab) extractRole(rawJSON []byte) (string, error) {
|
|
||||||
if s.roleAttributePath == "" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
role, err := s.searchJSONForStringAttr(s.roleAttributePath, rawJSON)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return role, nil
|
|
||||||
}
|
|
||||||
|
@ -3,6 +3,7 @@ package social
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -13,6 +14,7 @@ import (
|
|||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
@ -133,7 +135,7 @@ func ProvideService(cfg *setting.Cfg) *SocialService {
|
|||||||
// GitHub.
|
// GitHub.
|
||||||
if name == "github" {
|
if name == "github" {
|
||||||
ss.socialMap["github"] = &SocialGithub{
|
ss.socialMap["github"] = &SocialGithub{
|
||||||
SocialBase: newSocialBase(name, &config, info),
|
SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole),
|
||||||
apiUrl: info.ApiUrl,
|
apiUrl: info.ApiUrl,
|
||||||
teamIds: sec.Key("team_ids").Ints(","),
|
teamIds: sec.Key("team_ids").Ints(","),
|
||||||
allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()),
|
allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()),
|
||||||
@ -143,18 +145,16 @@ func ProvideService(cfg *setting.Cfg) *SocialService {
|
|||||||
// GitLab.
|
// GitLab.
|
||||||
if name == "gitlab" {
|
if name == "gitlab" {
|
||||||
ss.socialMap["gitlab"] = &SocialGitlab{
|
ss.socialMap["gitlab"] = &SocialGitlab{
|
||||||
SocialBase: newSocialBase(name, &config, info),
|
SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole),
|
||||||
apiUrl: info.ApiUrl,
|
apiUrl: info.ApiUrl,
|
||||||
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
|
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
|
||||||
roleAttributePath: info.RoleAttributePath,
|
|
||||||
roleAttributeStrict: info.RoleAttributeStrict,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Google.
|
// Google.
|
||||||
if name == "google" {
|
if name == "google" {
|
||||||
ss.socialMap["google"] = &SocialGoogle{
|
ss.socialMap["google"] = &SocialGoogle{
|
||||||
SocialBase: newSocialBase(name, &config, info),
|
SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole),
|
||||||
hostedDomain: info.HostedDomain,
|
hostedDomain: info.HostedDomain,
|
||||||
apiUrl: info.ApiUrl,
|
apiUrl: info.ApiUrl,
|
||||||
}
|
}
|
||||||
@ -163,7 +163,7 @@ func ProvideService(cfg *setting.Cfg) *SocialService {
|
|||||||
// AzureAD.
|
// AzureAD.
|
||||||
if name == "azuread" {
|
if name == "azuread" {
|
||||||
ss.socialMap["azuread"] = &SocialAzureAD{
|
ss.socialMap["azuread"] = &SocialAzureAD{
|
||||||
SocialBase: newSocialBase(name, &config, info),
|
SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole),
|
||||||
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
|
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
|
||||||
autoAssignOrgRole: cfg.AutoAssignOrgRole,
|
autoAssignOrgRole: cfg.AutoAssignOrgRole,
|
||||||
roleAttributeStrict: info.RoleAttributeStrict,
|
roleAttributeStrict: info.RoleAttributeStrict,
|
||||||
@ -173,7 +173,7 @@ func ProvideService(cfg *setting.Cfg) *SocialService {
|
|||||||
// Okta
|
// Okta
|
||||||
if name == "okta" {
|
if name == "okta" {
|
||||||
ss.socialMap["okta"] = &SocialOkta{
|
ss.socialMap["okta"] = &SocialOkta{
|
||||||
SocialBase: newSocialBase(name, &config, info),
|
SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole),
|
||||||
apiUrl: info.ApiUrl,
|
apiUrl: info.ApiUrl,
|
||||||
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
|
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
|
||||||
roleAttributePath: info.RoleAttributePath,
|
roleAttributePath: info.RoleAttributePath,
|
||||||
@ -184,7 +184,7 @@ func ProvideService(cfg *setting.Cfg) *SocialService {
|
|||||||
// Generic - Uses the same scheme as GitHub.
|
// Generic - Uses the same scheme as GitHub.
|
||||||
if name == "generic_oauth" {
|
if name == "generic_oauth" {
|
||||||
ss.socialMap["generic_oauth"] = &SocialGenericOAuth{
|
ss.socialMap["generic_oauth"] = &SocialGenericOAuth{
|
||||||
SocialBase: newSocialBase(name, &config, info),
|
SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole),
|
||||||
apiUrl: info.ApiUrl,
|
apiUrl: info.ApiUrl,
|
||||||
teamsUrl: info.TeamsUrl,
|
teamsUrl: info.TeamsUrl,
|
||||||
emailAttributeName: info.EmailAttributeName,
|
emailAttributeName: info.EmailAttributeName,
|
||||||
@ -215,7 +215,8 @@ func ProvideService(cfg *setting.Cfg) *SocialService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ss.socialMap[grafanaCom] = &SocialGrafanaCom{
|
ss.socialMap[grafanaCom] = &SocialGrafanaCom{
|
||||||
SocialBase: newSocialBase(name, &config, info),
|
SocialBase: newSocialBase(name, &config, info,
|
||||||
|
cfg.AutoAssignOrgRole),
|
||||||
url: cfg.GrafanaComURL,
|
url: cfg.GrafanaComURL,
|
||||||
allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()),
|
allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()),
|
||||||
}
|
}
|
||||||
@ -234,6 +235,11 @@ type BasicUserInfo struct {
|
|||||||
Groups []string
|
Groups []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BasicUserInfo) String() string {
|
||||||
|
return fmt.Sprintf("Id: %s, Name: %s, Email: %s, Login: %s, Company: %s, Role: %s, Groups: %v",
|
||||||
|
b.Id, b.Name, b.Email, b.Login, b.Company, b.Role, b.Groups)
|
||||||
|
}
|
||||||
|
|
||||||
type SocialConnector interface {
|
type SocialConnector interface {
|
||||||
Type() int
|
Type() int
|
||||||
UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error)
|
UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error)
|
||||||
@ -251,6 +257,10 @@ type SocialBase struct {
|
|||||||
log log.Logger
|
log log.Logger
|
||||||
allowSignup bool
|
allowSignup bool
|
||||||
allowedDomains []string
|
allowedDomains []string
|
||||||
|
|
||||||
|
roleAttributePath string
|
||||||
|
roleAttributeStrict bool
|
||||||
|
autoAssignOrgRole string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Error struct {
|
type Error struct {
|
||||||
@ -279,7 +289,11 @@ type Service interface {
|
|||||||
GetOAuthInfoProviders() map[string]*OAuthInfo
|
GetOAuthInfoProviders() map[string]*OAuthInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSocialBase(name string, config *oauth2.Config, info *OAuthInfo) *SocialBase {
|
func newSocialBase(name string,
|
||||||
|
config *oauth2.Config,
|
||||||
|
info *OAuthInfo,
|
||||||
|
autoAssignOrgRole string,
|
||||||
|
) *SocialBase {
|
||||||
logger := log.New("oauth." + name)
|
logger := log.New("oauth." + name)
|
||||||
|
|
||||||
return &SocialBase{
|
return &SocialBase{
|
||||||
@ -287,9 +301,40 @@ func newSocialBase(name string, config *oauth2.Config, info *OAuthInfo) *SocialB
|
|||||||
log: logger,
|
log: logger,
|
||||||
allowSignup: info.AllowSignup,
|
allowSignup: info.AllowSignup,
|
||||||
allowedDomains: info.AllowedDomains,
|
allowedDomains: info.AllowedDomains,
|
||||||
|
autoAssignOrgRole: autoAssignOrgRole,
|
||||||
|
roleAttributePath: info.RoleAttributePath,
|
||||||
|
roleAttributeStrict: info.RoleAttributeStrict,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type groupStruct struct {
|
||||||
|
Groups []string `json:"groups"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SocialBase) extractRole(rawJSON []byte, groups []string) (models.RoleType, error) {
|
||||||
|
if s.roleAttributePath == "" {
|
||||||
|
if s.autoAssignOrgRole != "" {
|
||||||
|
return models.RoleType(s.autoAssignOrgRole), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
role, err := s.searchJSONForStringAttr(s.roleAttributePath, rawJSON)
|
||||||
|
if err == nil && role != "" {
|
||||||
|
return models.RoleType(role), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupBytes, err := json.Marshal(groupStruct{groups}); err == nil {
|
||||||
|
if role, err := s.searchJSONForStringAttr(
|
||||||
|
s.roleAttributePath, groupBytes); err == nil && role != "" {
|
||||||
|
return models.RoleType(role), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetOAuthProviders returns available oauth providers and if they're enabled or not
|
// GetOAuthProviders returns available oauth providers and if they're enabled or not
|
||||||
func (ss *SocialService) GetOAuthProviders() map[string]bool {
|
func (ss *SocialService) GetOAuthProviders() map[string]bool {
|
||||||
result := map[string]bool{}
|
result := map[string]bool{}
|
||||||
|
Loading…
Reference in New Issue
Block a user