mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* Support `role_attribute_path` for GitLab OAuth Allow role mapping for GitLab accounts. Example: [auth.gitlab] role_attribute_path = is_admin && 'Admin' || 'Viewer' * Support `role_attribute_path` for GitLab OAuth Allow role mapping for GitLab accounts. Example: [auth.gitlab] role_attribute_path = is_admin && 'Admin' || 'Viewer' * docs: add docs for role_attribute_path * Apply suggestions from code review Co-authored-by: Peter Leitzen <splattael@users.noreply.github.com> * docs: update example example should suggest a full configuration * Apply suggestions from code review Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> * Apply suggestions from code review Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> * docs: add suggestions from tech writers Co-authored-by: Henry Sachs <Henry.Sachs@deutschebahn.com> Co-authored-by: Henry Sachs <henrysachs@gmail.com> Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com> Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com>
151 lines
3.1 KiB
Go
151 lines
3.1 KiB
Go
package social
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"regexp"
|
|
|
|
"github.com/grafana/grafana/pkg/models"
|
|
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
type SocialGitlab struct {
|
|
*SocialBase
|
|
allowedGroups []string
|
|
apiUrl string
|
|
roleAttributePath string
|
|
}
|
|
|
|
func (s *SocialGitlab) Type() int {
|
|
return int(models.GITLAB)
|
|
}
|
|
|
|
func (s *SocialGitlab) IsGroupMember(groups []string) bool {
|
|
if len(s.allowedGroups) == 0 {
|
|
return true
|
|
}
|
|
|
|
for _, allowedGroup := range s.allowedGroups {
|
|
for _, group := range groups {
|
|
if group == allowedGroup {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (s *SocialGitlab) GetGroups(client *http.Client) []string {
|
|
groups := make([]string, 0)
|
|
|
|
for page, url := s.GetGroupsPage(client, s.apiUrl+"/groups"); page != nil; page, url = s.GetGroupsPage(client, url) {
|
|
groups = append(groups, page...)
|
|
}
|
|
|
|
return groups
|
|
}
|
|
|
|
// GetGroupsPage returns groups and link to the next page if response is paginated
|
|
func (s *SocialGitlab) GetGroupsPage(client *http.Client, url string) ([]string, string) {
|
|
type Group struct {
|
|
FullPath string `json:"full_path"`
|
|
}
|
|
|
|
var (
|
|
groups []Group
|
|
next string
|
|
)
|
|
|
|
if url == "" {
|
|
return nil, next
|
|
}
|
|
|
|
response, err := s.httpGet(client, url)
|
|
if err != nil {
|
|
s.log.Error("Error getting groups from GitLab API", "err", err)
|
|
return nil, next
|
|
}
|
|
|
|
if err := json.Unmarshal(response.Body, &groups); err != nil {
|
|
s.log.Error("Error parsing JSON from GitLab API", "err", err)
|
|
return nil, next
|
|
}
|
|
|
|
fullPaths := make([]string, len(groups))
|
|
for i, group := range groups {
|
|
fullPaths[i] = group.FullPath
|
|
}
|
|
|
|
// GitLab uses Link header with "rel" set to prev/next/first/last page. We need "next".
|
|
if link, ok := response.Headers["Link"]; ok {
|
|
pattern := regexp.MustCompile(`<([^>]+)>; rel="next"`)
|
|
if matches := pattern.FindStringSubmatch(link[0]); matches != nil {
|
|
next = matches[1]
|
|
}
|
|
}
|
|
|
|
return fullPaths, next
|
|
}
|
|
|
|
func (s *SocialGitlab) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
|
|
var data struct {
|
|
Id int
|
|
Username string
|
|
Email string
|
|
Name string
|
|
State string
|
|
}
|
|
|
|
response, err := s.httpGet(client, s.apiUrl+"/user")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error getting user info: %s", err)
|
|
}
|
|
|
|
err = json.Unmarshal(response.Body, &data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting user info: %s", err)
|
|
}
|
|
|
|
if data.State != "active" {
|
|
return nil, fmt.Errorf("user %s is inactive", data.Username)
|
|
}
|
|
|
|
groups := s.GetGroups(client)
|
|
|
|
role, err := s.extractRole(response.Body)
|
|
if err != nil {
|
|
s.log.Error("Failed to extract role", "error", err)
|
|
}
|
|
|
|
userInfo := &BasicUserInfo{
|
|
Id: fmt.Sprintf("%d", data.Id),
|
|
Name: data.Name,
|
|
Login: data.Username,
|
|
Email: data.Email,
|
|
Groups: groups,
|
|
Role: role,
|
|
}
|
|
|
|
if !s.IsGroupMember(groups) {
|
|
return nil, errMissingGroupMembership
|
|
}
|
|
|
|
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
|
|
}
|