Commit c2affdee by Alexander Zobnin Committed by GitHub

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
parent 4e27ba96
......@@ -34,7 +34,7 @@ ldap_sync_ttl = 60
# Example `whitelist = 192.168.1.1, 192.168.1.0/24, 2001::23, 2001::0/120`
whitelist =
# 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 =
```
......
......@@ -171,6 +171,7 @@ func (hs *HTTPServer) OAuthLogin(ctx *m.ReqContext) {
Login: userInfo.Login,
Email: userInfo.Email,
OrgRoles: map[int64]m.RoleType{},
Groups: userInfo.Groups,
}
if userInfo.Role != "" {
......
......@@ -2,6 +2,7 @@ package social
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"regexp"
......@@ -20,6 +21,15 @@ type SocialGithub struct {
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 (
ErrMissingTeamMembership = &Error{"User not a member of one of the required teams"}
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 _, membershipId := range teamMemberships {
if teamId == membershipId {
for _, membership := range teamMemberships {
if teamId == membership.Id {
return true
}
}
......@@ -108,14 +118,10 @@ func (s *SocialGithub) FetchPrivateEmail(client *http.Client) (string, error) {
return email, nil
}
func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]int, error) {
type Record struct {
Id int `json:"id"`
}
func (s *SocialGithub) FetchTeamMemberships(client *http.Client) ([]GithubTeam, error) {
url := fmt.Sprintf(s.apiUrl + "/teams?per_page=100")
hasMore := true
ids := make([]int, 0)
teams := make([]GithubTeam, 0)
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)
}
var records []Record
var records []GithubTeam
err = json.Unmarshal(response.Body, &records)
if err != nil {
return nil, fmt.Errorf("Error getting team memberships: %s", err)
}
newRecords := len(records)
existingRecords := len(ids)
tempIds := make([]int, (newRecords + existingRecords))
copy(tempIds, ids)
ids = tempIds
for i, record := range records {
ids[i] = record.Id
}
teams = append(teams, records...)
url, hasMore = s.HasMoreRecords(response.Headers)
}
return ids, nil
return teams, nil
}
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)
}
teamMemberships, err := s.FetchTeamMemberships(client)
if err != nil {
return nil, fmt.Errorf("Error getting user teams: %s", err)
}
teams := convertToGroupList(teamMemberships)
userInfo := &BasicUserInfo{
Name: data.Login,
Login: data.Login,
Id: fmt.Sprintf("%d", data.Id),
Email: data.Email,
Name: data.Login,
Login: data.Login,
Id: fmt.Sprintf("%d", data.Id),
Email: data.Email,
Groups: teams,
}
organizationsUrl := fmt.Sprintf(s.apiUrl + "/orgs")
......@@ -236,3 +242,26 @@ func (s *SocialGithub) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
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
Company string
Role string
Groups []string
}
type SocialConnector interface {
......
......@@ -14,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/services/ldap"
"github.com/grafana/grafana/pkg/services/multildap"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
const (
......@@ -246,13 +247,17 @@ func (auth *AuthProxy) LoginViaHeader() (int64, error) {
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] == "" {
continue
}
if val := auth.ctx.Req.Header.Get(auth.headers[field]); val != "" {
reflect.ValueOf(extUser).Elem().FieldByName(field).SetString(val)
if field == "Groups" {
extUser.Groups = util.SplitString(val)
} else {
reflect.ValueOf(extUser).Elem().FieldByName(field).SetString(val)
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment