mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
OAuth: return github teams as a part of user info (enable team sync) (#17797)
* OAuth: github team sync POC * OAuth: minor refactor of github module * OAuth: able to use team shorthands for github team sync * support passing a list of groups via auth-proxy header
This commit is contained in:
parent
4e27ba9646
commit
c2affdee1e
@ -34,7 +34,7 @@ ldap_sync_ttl = 60
|
|||||||
# Example `whitelist = 192.168.1.1, 192.168.1.0/24, 2001::23, 2001::0/120`
|
# Example `whitelist = 192.168.1.1, 192.168.1.0/24, 2001::23, 2001::0/120`
|
||||||
whitelist =
|
whitelist =
|
||||||
# Optionally define more headers to sync other user attributes
|
# Optionally define more headers to sync other user attributes
|
||||||
# Example `headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL`
|
# Example `headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL Groups:X-WEBAUTH-GROUPS`
|
||||||
headers =
|
headers =
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -171,6 +171,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *m.ReqContext) {
|
|||||||
Login: userInfo.Login,
|
Login: userInfo.Login,
|
||||||
Email: userInfo.Email,
|
Email: userInfo.Email,
|
||||||
OrgRoles: map[int64]m.RoleType{},
|
OrgRoles: map[int64]m.RoleType{},
|
||||||
|
Groups: userInfo.Groups,
|
||||||
}
|
}
|
||||||
|
|
||||||
if userInfo.Role != "" {
|
if userInfo.Role != "" {
|
||||||
|
@ -2,6 +2,7 @@ package social
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -20,6 +21,15 @@ type SocialGithub struct {
|
|||||||
teamIds []int
|
teamIds []int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GithubTeam struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Slug string `json:"slug"`
|
||||||
|
URL string `json:"html_url"`
|
||||||
|
Organization struct {
|
||||||
|
Login string `json:"login"`
|
||||||
|
} `json:"organization"`
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrMissingTeamMembership = &Error{"User not a member of one of the required teams"}
|
ErrMissingTeamMembership = &Error{"User not a member of one of the required teams"}
|
||||||
ErrMissingOrganizationMembership = &Error{"User not a member of one of the required organizations"}
|
ErrMissingOrganizationMembership = &Error{"User not a member of one of the required organizations"}
|
||||||
@ -48,8 +58,8 @@ func (s *SocialGithub) IsTeamMember(client *http.Client) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, teamId := range s.teamIds {
|
for _, teamId := range s.teamIds {
|
||||||
for _, membershipId := range teamMemberships {
|
for _, membership := range teamMemberships {
|
||||||
if teamId == membershipId {
|
if teamId == membership.Id {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -108,14 +118,10 @@ func (s *SocialGithub) FetchPrivateEmail(client *http.Client) (string, error) {
|
|||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]int, error) {
|
func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]GithubTeam, error) {
|
||||||
type Record struct {
|
|
||||||
Id int `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
url := fmt.Sprintf(s.apiUrl + "/teams?per_page=100")
|
url := fmt.Sprintf(s.apiUrl + "/teams?per_page=100")
|
||||||
hasMore := true
|
hasMore := true
|
||||||
ids := make([]int, 0)
|
teams := make([]GithubTeam, 0)
|
||||||
|
|
||||||
for hasMore {
|
for hasMore {
|
||||||
|
|
||||||
@ -124,27 +130,19 @@ func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]int, error)
|
|||||||
return nil, fmt.Errorf("Error getting team memberships: %s", err)
|
return nil, fmt.Errorf("Error getting team memberships: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var records []Record
|
var records []GithubTeam
|
||||||
|
|
||||||
err = json.Unmarshal(response.Body, &records)
|
err = json.Unmarshal(response.Body, &records)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error getting team memberships: %s", err)
|
return nil, fmt.Errorf("Error getting team memberships: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newRecords := len(records)
|
teams = append(teams, records...)
|
||||||
existingRecords := len(ids)
|
|
||||||
tempIds := make([]int, (newRecords + existingRecords))
|
|
||||||
copy(tempIds, ids)
|
|
||||||
ids = tempIds
|
|
||||||
|
|
||||||
for i, record := range records {
|
|
||||||
ids[i] = record.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
url, hasMore = s.HasMoreRecords(response.Headers)
|
url, hasMore = s.HasMoreRecords(response.Headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ids, nil
|
return teams, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SocialGithub) HasMoreRecords(headers http.Header) (string, bool) {
|
func (s *SocialGithub) HasMoreRecords(headers http.Header) (string, bool) {
|
||||||
@ -210,11 +208,19 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
teamMemberships, err := s.FetchTeamMemberships(client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error getting user teams: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
teams := convertToGroupList(teamMemberships)
|
||||||
|
|
||||||
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,
|
||||||
|
Groups: teams,
|
||||||
}
|
}
|
||||||
|
|
||||||
organizationsUrl := fmt.Sprintf(s.apiUrl + "/orgs")
|
organizationsUrl := fmt.Sprintf(s.apiUrl + "/orgs")
|
||||||
@ -236,3 +242,26 @@ func (s *SocialGithub) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
|
|||||||
|
|
||||||
return userInfo, nil
|
return userInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *GithubTeam) GetShorthand() (string, error) {
|
||||||
|
if t.Organization.Login == "" || t.Slug == "" {
|
||||||
|
return "", errors.New("Error getting team shorthand")
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("@%s/%s", t.Organization.Login, t.Slug), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertToGroupList(t []GithubTeam) []string {
|
||||||
|
groups := make([]string, 0)
|
||||||
|
for _, team := range t {
|
||||||
|
// Group shouldn't be empty string, otherwise team sync will not work properly
|
||||||
|
if team.URL != "" {
|
||||||
|
groups = append(groups, team.URL)
|
||||||
|
}
|
||||||
|
teamShorthand, _ := team.GetShorthand()
|
||||||
|
if teamShorthand != "" {
|
||||||
|
groups = append(groups, teamShorthand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@ type BasicUserInfo struct {
|
|||||||
Login string
|
Login string
|
||||||
Company string
|
Company string
|
||||||
Role string
|
Role string
|
||||||
|
Groups []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SocialConnector interface {
|
type SocialConnector interface {
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/ldap"
|
"github.com/grafana/grafana/pkg/services/ldap"
|
||||||
"github.com/grafana/grafana/pkg/services/multildap"
|
"github.com/grafana/grafana/pkg/services/multildap"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -246,15 +247,19 @@ func (auth *AuthProxy) LoginViaHeader() (int64, error) {
|
|||||||
return 0, newError("Auth proxy header property invalid", nil)
|
return 0, newError("Auth proxy header property invalid", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, field := range []string{"Name", "Email", "Login"} {
|
for _, field := range []string{"Name", "Email", "Login", "Groups"} {
|
||||||
if auth.headers[field] == "" {
|
if auth.headers[field] == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if val := auth.ctx.Req.Header.Get(auth.headers[field]); val != "" {
|
if val := auth.ctx.Req.Header.Get(auth.headers[field]); val != "" {
|
||||||
|
if field == "Groups" {
|
||||||
|
extUser.Groups = util.SplitString(val)
|
||||||
|
} else {
|
||||||
reflect.ValueOf(extUser).Elem().FieldByName(field).SetString(val)
|
reflect.ValueOf(extUser).Elem().FieldByName(field).SetString(val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
upsert := &models.UpsertUserCommand{
|
upsert := &models.UpsertUserCommand{
|
||||||
ReqContext: auth.ctx,
|
ReqContext: auth.ctx,
|
||||||
|
Loading…
Reference in New Issue
Block a user