OAuth: Allow assigning Server Admin (#54780)
* extract errors to errors file * implement oauth server admin assignment * add server admin tests * deduplicate autoAssignOrgRole * deduplicate strict setting * deduplicate strict setting * add support for generic oauth * add role attribute strict support for generic oauth * add support for github/gitlab * assignGrafanaAdmin option is here to stay * unify similar errors * add config option * add okta server admin mapping * remove never used Company attribute * unify generic oauth role extract with other methods * case insensitive role match as in azure * add ini settings * add server admin to devenv * remove duplicate fields * add documentation to oauth * fix titlecase test * implement doc feedback
This commit is contained in:
@@ -263,14 +263,15 @@ func (hs *HTTPServer) buildExternalUserInfo(token *oauth2.Token, userInfo *socia
|
||||
oauthLogger.Debug("Building external user info from OAuth user info")
|
||||
|
||||
extUser := &models.ExternalUserInfo{
|
||||
AuthModule: fmt.Sprintf("oauth_%s", name),
|
||||
OAuthToken: token,
|
||||
AuthId: userInfo.Id,
|
||||
Name: userInfo.Name,
|
||||
Login: userInfo.Login,
|
||||
Email: userInfo.Email,
|
||||
OrgRoles: map[int64]org.RoleType{},
|
||||
Groups: userInfo.Groups,
|
||||
AuthModule: fmt.Sprintf("oauth_%s", name),
|
||||
OAuthToken: token,
|
||||
AuthId: userInfo.Id,
|
||||
Name: userInfo.Name,
|
||||
Login: userInfo.Login,
|
||||
Email: userInfo.Email,
|
||||
OrgRoles: map[int64]org.RoleType{},
|
||||
Groups: userInfo.Groups,
|
||||
IsGrafanaAdmin: userInfo.IsGrafanaAdmin,
|
||||
}
|
||||
|
||||
if userInfo.Role != "" && !hs.Cfg.OAuthSkipOrgRoleUpdateSync {
|
||||
|
||||
@@ -3,7 +3,6 @@ package social
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -18,9 +17,7 @@ import (
|
||||
|
||||
type SocialAzureAD struct {
|
||||
*SocialBase
|
||||
allowedGroups []string
|
||||
autoAssignOrgRole string
|
||||
roleAttributeStrict bool
|
||||
allowedGroups []string
|
||||
}
|
||||
|
||||
type azureClaims struct {
|
||||
@@ -53,7 +50,7 @@ func (s *SocialAzureAD) Type() int {
|
||||
func (s *SocialAzureAD) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
|
||||
idToken := token.Extra("id_token")
|
||||
if idToken == nil {
|
||||
return nil, fmt.Errorf("no id_token found")
|
||||
return nil, ErrIDTokenNotFound
|
||||
}
|
||||
|
||||
parsedToken, err := jwt.ParseSigned(idToken.(string))
|
||||
@@ -68,12 +65,12 @@ func (s *SocialAzureAD) UserInfo(client *http.Client, token *oauth2.Token) (*Bas
|
||||
|
||||
email := claims.extractEmail()
|
||||
if email == "" {
|
||||
return nil, errors.New("error getting user info: no email found in access token")
|
||||
return nil, ErrEmailNotFound
|
||||
}
|
||||
|
||||
role := claims.extractRole(s.autoAssignOrgRole, s.roleAttributeStrict)
|
||||
role, grafanaAdmin := claims.extractRoleAndAdmin(s.autoAssignOrgRole, s.roleAttributeStrict)
|
||||
if role == "" {
|
||||
return nil, errors.New("user does not have a valid role")
|
||||
return nil, ErrInvalidBasicRole
|
||||
}
|
||||
logger.Debug("AzureAD OAuth: extracted role", "email", email, "role", role)
|
||||
|
||||
@@ -87,13 +84,19 @@ func (s *SocialAzureAD) UserInfo(client *http.Client, token *oauth2.Token) (*Bas
|
||||
return nil, errMissingGroupMembership
|
||||
}
|
||||
|
||||
var isGrafanaAdmin *bool = nil
|
||||
if s.allowAssignGrafanaAdmin {
|
||||
isGrafanaAdmin = &grafanaAdmin
|
||||
}
|
||||
|
||||
return &BasicUserInfo{
|
||||
Id: claims.ID,
|
||||
Name: claims.Name,
|
||||
Email: email,
|
||||
Login: email,
|
||||
Role: string(role),
|
||||
Groups: groups,
|
||||
Id: claims.ID,
|
||||
Name: claims.Name,
|
||||
Email: email,
|
||||
Login: email,
|
||||
Role: string(role),
|
||||
IsGrafanaAdmin: isGrafanaAdmin,
|
||||
Groups: groups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -123,16 +126,18 @@ func (claims *azureClaims) extractEmail() string {
|
||||
return claims.Email
|
||||
}
|
||||
|
||||
func (claims *azureClaims) extractRole(autoAssignRole string, strictMode bool) org.RoleType {
|
||||
// extractRoleAndAdmin extracts the role from the claims and returns the role and whether the user is a Grafana admin.
|
||||
func (claims *azureClaims) extractRoleAndAdmin(autoAssignRole string, strictMode bool) (org.RoleType, bool) {
|
||||
if len(claims.Roles) == 0 {
|
||||
if strictMode {
|
||||
return org.RoleType("")
|
||||
return org.RoleType(""), false
|
||||
}
|
||||
|
||||
return org.RoleType(autoAssignRole)
|
||||
return org.RoleType(autoAssignRole), false
|
||||
}
|
||||
|
||||
roleOrder := []org.RoleType{
|
||||
RoleGrafanaAdmin,
|
||||
org.RoleAdmin,
|
||||
org.RoleEditor,
|
||||
org.RoleViewer,
|
||||
@@ -140,15 +145,19 @@ func (claims *azureClaims) extractRole(autoAssignRole string, strictMode bool) o
|
||||
|
||||
for _, role := range roleOrder {
|
||||
if found := hasRole(claims.Roles, role); found {
|
||||
return role
|
||||
if role == RoleGrafanaAdmin {
|
||||
return org.RoleAdmin, true
|
||||
}
|
||||
|
||||
return role, false
|
||||
}
|
||||
}
|
||||
|
||||
if strictMode {
|
||||
return org.RoleType("")
|
||||
return org.RoleType(""), false
|
||||
}
|
||||
|
||||
return org.RoleViewer
|
||||
return org.RoleViewer, false
|
||||
}
|
||||
|
||||
func hasRole(roles []string, role org.RoleType) bool {
|
||||
@@ -157,6 +166,7 @@ func hasRole(roles []string, role org.RoleType) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,20 @@ import (
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
)
|
||||
|
||||
func trueBoolPtr() *bool {
|
||||
b := true
|
||||
return &b
|
||||
}
|
||||
|
||||
func falseBoolPtr() *bool {
|
||||
b := false
|
||||
return &b
|
||||
}
|
||||
|
||||
func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
type fields struct {
|
||||
SocialBase *SocialBase
|
||||
allowedGroups []string
|
||||
autoAssignOrgRole string
|
||||
roleAttributeStrict bool
|
||||
SocialBase *SocialBase
|
||||
allowedGroups []string
|
||||
}
|
||||
type args struct {
|
||||
client *http.Client
|
||||
@@ -46,16 +54,15 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
ID: "1234",
|
||||
},
|
||||
fields: fields{
|
||||
autoAssignOrgRole: "Viewer",
|
||||
SocialBase: &SocialBase{autoAssignOrgRole: "Viewer"},
|
||||
},
|
||||
want: &BasicUserInfo{
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Company: "",
|
||||
Role: "Viewer",
|
||||
Groups: []string{},
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Role: "Viewer",
|
||||
Groups: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -86,16 +93,15 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
ID: "1234",
|
||||
},
|
||||
fields: fields{
|
||||
autoAssignOrgRole: "Viewer",
|
||||
SocialBase: &SocialBase{autoAssignOrgRole: "Viewer"},
|
||||
},
|
||||
want: &BasicUserInfo{
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Company: "",
|
||||
Role: "Viewer",
|
||||
Groups: []string{},
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Role: "Viewer",
|
||||
Groups: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -108,13 +114,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
ID: "1234",
|
||||
},
|
||||
want: &BasicUserInfo{
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Company: "",
|
||||
Role: "Admin",
|
||||
Groups: []string{},
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Role: "Admin",
|
||||
Groups: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -127,13 +132,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
ID: "1234",
|
||||
},
|
||||
want: &BasicUserInfo{
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Company: "",
|
||||
Role: "Admin",
|
||||
Groups: []string{},
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Role: "Admin",
|
||||
Groups: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -146,13 +150,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
ID: "1234",
|
||||
},
|
||||
want: &BasicUserInfo{
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Company: "",
|
||||
Role: "Viewer",
|
||||
Groups: []string{},
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Role: "Viewer",
|
||||
Groups: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -165,16 +168,15 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
ID: "1234",
|
||||
},
|
||||
fields: fields{
|
||||
autoAssignOrgRole: "Editor",
|
||||
SocialBase: &SocialBase{autoAssignOrgRole: "Editor"},
|
||||
},
|
||||
want: &BasicUserInfo{
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Company: "",
|
||||
Role: "Editor",
|
||||
Groups: []string{},
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Role: "Editor",
|
||||
Groups: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -187,13 +189,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
ID: "1234",
|
||||
},
|
||||
want: &BasicUserInfo{
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Company: "",
|
||||
Role: "Editor",
|
||||
Groups: []string{},
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Role: "Editor",
|
||||
Groups: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -206,13 +207,74 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
ID: "1234",
|
||||
},
|
||||
want: &BasicUserInfo{
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Company: "",
|
||||
Role: "Admin",
|
||||
Groups: []string{},
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Role: "Admin",
|
||||
Groups: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Grafana Admin but setting is disabled",
|
||||
fields: fields{SocialBase: &SocialBase{allowAssignGrafanaAdmin: false}},
|
||||
claims: &azureClaims{
|
||||
Email: "me@example.com",
|
||||
PreferredUsername: "",
|
||||
Roles: []string{"GrafanaAdmin"},
|
||||
Name: "My Name",
|
||||
ID: "1234",
|
||||
},
|
||||
want: &BasicUserInfo{
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Role: "Admin",
|
||||
Groups: []string{},
|
||||
IsGrafanaAdmin: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Editor roles in claim and GrafanaAdminAssignment enabled",
|
||||
fields: fields{
|
||||
SocialBase: newSocialBase("azuread",
|
||||
&oauth2.Config{}, &OAuthInfo{AllowAssignGrafanaAdmin: true}, "")},
|
||||
claims: &azureClaims{
|
||||
Email: "me@example.com",
|
||||
PreferredUsername: "",
|
||||
Roles: []string{"Editor"},
|
||||
Name: "My Name",
|
||||
ID: "1234",
|
||||
},
|
||||
want: &BasicUserInfo{
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Role: "Editor",
|
||||
Groups: []string{},
|
||||
IsGrafanaAdmin: falseBoolPtr(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Grafana Admin and Editor roles in claim",
|
||||
fields: fields{SocialBase: &SocialBase{allowAssignGrafanaAdmin: true}},
|
||||
claims: &azureClaims{
|
||||
Email: "me@example.com",
|
||||
PreferredUsername: "",
|
||||
Roles: []string{"GrafanaAdmin", "Editor"},
|
||||
Name: "My Name",
|
||||
ID: "1234",
|
||||
},
|
||||
want: &BasicUserInfo{
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Role: "Admin",
|
||||
Groups: []string{},
|
||||
IsGrafanaAdmin: trueBoolPtr(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -234,8 +296,8 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
{
|
||||
name: "Error if user is a member of allowed_groups",
|
||||
fields: fields{
|
||||
allowedGroups: []string{"foo", "bar"},
|
||||
autoAssignOrgRole: "Viewer",
|
||||
allowedGroups: []string{"foo", "bar"},
|
||||
SocialBase: &SocialBase{autoAssignOrgRole: "Viewer"},
|
||||
},
|
||||
claims: &azureClaims{
|
||||
Email: "me@example.com",
|
||||
@@ -246,13 +308,12 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
ID: "1234",
|
||||
},
|
||||
want: &BasicUserInfo{
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Company: "",
|
||||
Role: "Viewer",
|
||||
Groups: []string{"foo"},
|
||||
Id: "1234",
|
||||
Name: "My Name",
|
||||
Email: "me@example.com",
|
||||
Login: "me@example.com",
|
||||
Role: "Viewer",
|
||||
Groups: []string{"foo"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -283,7 +344,7 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
{
|
||||
name: "Fetch empty role when strict attribute role is true and no match",
|
||||
fields: fields{
|
||||
roleAttributeStrict: true,
|
||||
SocialBase: newSocialBase("azuread", &oauth2.Config{}, &OAuthInfo{RoleAttributeStrict: true}, ""),
|
||||
},
|
||||
claims: &azureClaims{
|
||||
Email: "me@example.com",
|
||||
@@ -299,7 +360,7 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
{
|
||||
name: "Fetch empty role when strict attribute role is true and no role claims returned",
|
||||
fields: fields{
|
||||
roleAttributeStrict: true,
|
||||
SocialBase: newSocialBase("azuread", &oauth2.Config{}, &OAuthInfo{RoleAttributeStrict: true}, ""),
|
||||
},
|
||||
claims: &azureClaims{
|
||||
Email: "me@example.com",
|
||||
@@ -313,13 +374,16 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := &SocialAzureAD{
|
||||
SocialBase: tt.fields.SocialBase,
|
||||
allowedGroups: tt.fields.allowedGroups,
|
||||
autoAssignOrgRole: tt.fields.autoAssignOrgRole,
|
||||
roleAttributeStrict: tt.fields.roleAttributeStrict,
|
||||
SocialBase: tt.fields.SocialBase,
|
||||
allowedGroups: tt.fields.allowedGroups,
|
||||
}
|
||||
|
||||
if tt.fields.SocialBase == nil {
|
||||
s.SocialBase = newSocialBase("azuread", &oauth2.Config{}, &OAuthInfo{}, "")
|
||||
}
|
||||
|
||||
key := []byte("secret")
|
||||
@@ -357,14 +421,10 @@ func TestSocialAzureAD_UserInfo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
raw, err = jwt.Signed(sig).Claims(cl).Claims(tt.claims).CompactSerialize()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
raw, err = jwt.Signed(sig).Claims(cl).CompactSerialize()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package social
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrIDTokenNotFound = errors.New("id_token not found")
|
||||
ErrInvalidBasicRole = errors.New("user does not have a valid basic role")
|
||||
ErrEmailNotFound = errors.New("error getting user info: no email found in access token")
|
||||
)
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
@@ -27,8 +26,6 @@ type SocialGenericOAuth struct {
|
||||
emailAttributePath string
|
||||
loginAttributePath string
|
||||
nameAttributePath string
|
||||
roleAttributePath string
|
||||
roleAttributeStrict bool
|
||||
groupsAttributePath string
|
||||
idTokenAttributeName string
|
||||
teamIdsAttributePath string
|
||||
@@ -147,12 +144,18 @@ func (s *SocialGenericOAuth) UserInfo(client *http.Client, token *oauth2.Token)
|
||||
}
|
||||
|
||||
if userInfo.Role == "" {
|
||||
role, err := s.extractRole(data)
|
||||
if err != nil {
|
||||
s.log.Warn("Failed to extract role", "error", err)
|
||||
} else if role != "" {
|
||||
role, grafanaAdmin := s.extractRoleAndAdmin(data.rawJSON, []string{})
|
||||
if role != "" {
|
||||
if s.roleAttributeStrict && !role.IsValid() {
|
||||
return nil, ErrInvalidBasicRole
|
||||
}
|
||||
|
||||
s.log.Debug("Setting user info role from extracted role")
|
||||
userInfo.Role = role
|
||||
|
||||
userInfo.Role = string(role)
|
||||
if s.allowAssignGrafanaAdmin {
|
||||
userInfo.IsGrafanaAdmin = &grafanaAdmin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,10 +184,6 @@ func (s *SocialGenericOAuth) UserInfo(client *http.Client, token *oauth2.Token)
|
||||
userInfo.Login = userInfo.Email
|
||||
}
|
||||
|
||||
if s.roleAttributeStrict && !org.RoleType(userInfo.Role).IsValid() {
|
||||
return nil, errors.New("invalid role")
|
||||
}
|
||||
|
||||
if !s.IsTeamMember(client) {
|
||||
return nil, errors.New("user not a member of one of the required teams")
|
||||
}
|
||||
@@ -355,19 +354,6 @@ func (s *SocialGenericOAuth) extractUserName(data *UserInfoJson) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *SocialGenericOAuth) extractRole(data *UserInfoJson) (string, error) {
|
||||
if s.roleAttributePath == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
role, err := s.searchJSONForStringAttr(s.roleAttributePath, data.rawJSON)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (s *SocialGenericOAuth) extractGroups(data *UserInfoJson) ([]string, error) {
|
||||
if s.groupsAttributePath == "" {
|
||||
return []string{}, nil
|
||||
|
||||
@@ -245,12 +245,14 @@ func TestUserInfoSearchesForEmailAndRole(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
Name string
|
||||
ResponseBody interface{}
|
||||
OAuth2Extra interface{}
|
||||
RoleAttributePath string
|
||||
ExpectedEmail string
|
||||
ExpectedRole string
|
||||
Name string
|
||||
AllowAssignGrafanaAdmin bool
|
||||
ResponseBody interface{}
|
||||
OAuth2Extra interface{}
|
||||
RoleAttributePath string
|
||||
ExpectedEmail string
|
||||
ExpectedRole string
|
||||
ExpectedGrafanaAdmin *bool
|
||||
}{
|
||||
{
|
||||
Name: "Given a valid id_token, a valid role path, no API response, use id_token",
|
||||
@@ -330,6 +332,38 @@ func TestUserInfoSearchesForEmailAndRole(t *testing.T) {
|
||||
ExpectedEmail: "john.doe@example.com",
|
||||
ExpectedRole: "Admin",
|
||||
},
|
||||
{
|
||||
Name: "Given a valid id_token and AssignGrafanaAdmin is unchecked, don't grant Server Admin",
|
||||
AllowAssignGrafanaAdmin: false,
|
||||
OAuth2Extra: map[string]interface{}{
|
||||
// { "role": "GrafanaAdmin", "email": "john.doe@example.com" }
|
||||
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiR3JhZmFuYUFkbWluIiwiZW1haWwiOiJqb2huLmRvZUBleGFtcGxlLmNvbSJ9.cQqMJpVjwdtJ8qEZLOo9RKNbAFfpkQcpnRG0nopmWEI",
|
||||
},
|
||||
ResponseBody: map[string]interface{}{
|
||||
"role": "FromResponse",
|
||||
"email": "from_response@example.com",
|
||||
},
|
||||
RoleAttributePath: "role",
|
||||
ExpectedEmail: "john.doe@example.com",
|
||||
ExpectedRole: "Admin",
|
||||
ExpectedGrafanaAdmin: nil,
|
||||
},
|
||||
{
|
||||
Name: "Given a valid id_token and AssignGrafanaAdmin is checked, grant Server Admin",
|
||||
AllowAssignGrafanaAdmin: true,
|
||||
OAuth2Extra: map[string]interface{}{
|
||||
// { "role": "GrafanaAdmin", "email": "john.doe@example.com" }
|
||||
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiR3JhZmFuYUFkbWluIiwiZW1haWwiOiJqb2huLmRvZUBleGFtcGxlLmNvbSJ9.cQqMJpVjwdtJ8qEZLOo9RKNbAFfpkQcpnRG0nopmWEI",
|
||||
},
|
||||
ResponseBody: map[string]interface{}{
|
||||
"role": "FromResponse",
|
||||
"email": "from_response@example.com",
|
||||
},
|
||||
RoleAttributePath: "role",
|
||||
ExpectedEmail: "john.doe@example.com",
|
||||
ExpectedRole: "Admin",
|
||||
ExpectedGrafanaAdmin: trueBoolPtr(),
|
||||
},
|
||||
{
|
||||
Name: "Given a valid id_token, an invalid role path, a valid API response, prefer id_token",
|
||||
OAuth2Extra: map[string]interface{}{
|
||||
@@ -368,7 +402,7 @@ func TestUserInfoSearchesForEmailAndRole(t *testing.T) {
|
||||
},
|
||||
RoleAttributePath: "role",
|
||||
ExpectedEmail: "john.doe@example.com",
|
||||
ExpectedRole: "FromResponse",
|
||||
ExpectedRole: "Fromresponse",
|
||||
},
|
||||
{
|
||||
Name: "Given a valid id_token, a valid advanced JMESPath role path, derive the role",
|
||||
@@ -416,6 +450,8 @@ func TestUserInfoSearchesForEmailAndRole(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
provider.roleAttributePath = test.RoleAttributePath
|
||||
provider.allowAssignGrafanaAdmin = test.AllowAssignGrafanaAdmin
|
||||
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
body, err := json.Marshal(test.ResponseBody)
|
||||
require.NoError(t, err)
|
||||
@@ -439,6 +475,7 @@ func TestUserInfoSearchesForEmailAndRole(t *testing.T) {
|
||||
require.Equal(t, test.ExpectedEmail, actualResult.Email)
|
||||
require.Equal(t, test.ExpectedEmail, actualResult.Login)
|
||||
require.Equal(t, test.ExpectedRole, actualResult.Role)
|
||||
require.Equal(t, test.ExpectedGrafanaAdmin, actualResult.IsGrafanaAdmin)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -201,21 +201,24 @@ func (s *SocialGithub) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
|
||||
|
||||
teams := convertToGroupList(teamMemberships)
|
||||
|
||||
role, err := s.extractRole(response.Body, teams)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to extract role", "error", err)
|
||||
}
|
||||
role, grafanaAdmin := s.extractRoleAndAdmin(response.Body, teams)
|
||||
if s.roleAttributeStrict && !role.IsValid() {
|
||||
return nil, errors.New("invalid role")
|
||||
return nil, ErrInvalidBasicRole
|
||||
}
|
||||
|
||||
var isGrafanaAdmin *bool = nil
|
||||
if s.allowAssignGrafanaAdmin {
|
||||
isGrafanaAdmin = &grafanaAdmin
|
||||
}
|
||||
|
||||
userInfo := &BasicUserInfo{
|
||||
Name: data.Login,
|
||||
Login: data.Login,
|
||||
Id: fmt.Sprintf("%d", data.Id),
|
||||
Email: data.Email,
|
||||
Role: string(role),
|
||||
Groups: teams,
|
||||
Name: data.Login,
|
||||
Login: data.Login,
|
||||
Id: fmt.Sprintf("%d", data.Id),
|
||||
Email: data.Email,
|
||||
Role: string(role),
|
||||
Groups: teams,
|
||||
IsGrafanaAdmin: isGrafanaAdmin,
|
||||
}
|
||||
if data.Name != "" {
|
||||
userInfo.Name = data.Name
|
||||
|
||||
@@ -127,13 +127,12 @@ func TestSocialGitHub_UserInfo(t *testing.T) {
|
||||
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"},
|
||||
Id: "1",
|
||||
Name: "monalisa octocat",
|
||||
Email: "octocat@github.com",
|
||||
Login: "octocat",
|
||||
Role: "",
|
||||
Groups: []string{"https://github.com/orgs/github/teams/justice-league", "@github/justice-league"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -143,13 +142,12 @@ func TestSocialGitHub_UserInfo(t *testing.T) {
|
||||
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"},
|
||||
Id: "1",
|
||||
Name: "monalisa octocat",
|
||||
Email: "octocat@github.com",
|
||||
Login: "octocat",
|
||||
Role: "Admin",
|
||||
Groups: []string{"https://github.com/orgs/github/teams/justice-league", "@github/justice-league"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -159,13 +157,12 @@ func TestSocialGitHub_UserInfo(t *testing.T) {
|
||||
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"},
|
||||
Id: "1",
|
||||
Name: "monalisa octocat",
|
||||
Email: "octocat@github.com",
|
||||
Login: "octocat",
|
||||
Role: "Editor",
|
||||
Groups: []string{"https://github.com/orgs/github/teams/justice-league", "@github/justice-league"},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -175,13 +172,12 @@ func TestSocialGitHub_UserInfo(t *testing.T) {
|
||||
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"},
|
||||
Id: "1",
|
||||
Name: "monalisa octocat",
|
||||
Email: "octocat@github.com",
|
||||
Login: "octocat",
|
||||
Role: "Editor",
|
||||
Groups: []string{"https://github.com/orgs/github/teams/justice-league", "@github/justice-league"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package social
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
@@ -114,21 +113,24 @@ func (s *SocialGitlab) UserInfo(client *http.Client, token *oauth2.Token) (*Basi
|
||||
|
||||
groups := s.GetGroups(client)
|
||||
|
||||
role, err := s.extractRole(response.Body, groups)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to extract role", "error", err)
|
||||
}
|
||||
role, grafanaAdmin := s.extractRoleAndAdmin(response.Body, groups)
|
||||
if s.roleAttributeStrict && !role.IsValid() {
|
||||
return nil, errors.New("invalid role")
|
||||
return nil, ErrInvalidBasicRole
|
||||
}
|
||||
|
||||
var isGrafanaAdmin *bool = nil
|
||||
if s.allowAssignGrafanaAdmin {
|
||||
isGrafanaAdmin = &grafanaAdmin
|
||||
}
|
||||
|
||||
userInfo := &BasicUserInfo{
|
||||
Id: fmt.Sprintf("%d", data.Id),
|
||||
Name: data.Name,
|
||||
Login: data.Username,
|
||||
Email: data.Email,
|
||||
Groups: groups,
|
||||
Role: string(role),
|
||||
Id: fmt.Sprintf("%d", data.Id),
|
||||
Name: data.Name,
|
||||
Login: data.Username,
|
||||
Email: data.Email,
|
||||
Groups: groups,
|
||||
Role: string(role),
|
||||
IsGrafanaAdmin: isGrafanaAdmin,
|
||||
}
|
||||
|
||||
if !s.IsGroupMember(groups) {
|
||||
|
||||
@@ -7,17 +7,14 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"golang.org/x/oauth2"
|
||||
"gopkg.in/square/go-jose.v2/jwt"
|
||||
)
|
||||
|
||||
type SocialOkta struct {
|
||||
*SocialBase
|
||||
apiUrl string
|
||||
allowedGroups []string
|
||||
roleAttributePath string
|
||||
roleAttributeStrict bool
|
||||
apiUrl string
|
||||
allowedGroups []string
|
||||
}
|
||||
|
||||
type OktaUserInfoJson struct {
|
||||
@@ -78,26 +75,29 @@ func (s *SocialOkta) UserInfo(client *http.Client, token *oauth2.Token) (*BasicU
|
||||
return nil, err
|
||||
}
|
||||
|
||||
role, err := s.extractRole(&data)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to extract role", "error", err)
|
||||
}
|
||||
if s.roleAttributeStrict && !org.RoleType(role).IsValid() {
|
||||
return nil, errors.New("invalid role")
|
||||
}
|
||||
|
||||
groups := s.GetGroups(&data)
|
||||
if !s.IsGroupMember(groups) {
|
||||
return nil, errMissingGroupMembership
|
||||
}
|
||||
|
||||
role, grafanaAdmin := s.extractRoleAndAdmin(data.rawJSON, groups)
|
||||
if s.roleAttributeStrict && !role.IsValid() {
|
||||
return nil, ErrInvalidBasicRole
|
||||
}
|
||||
|
||||
var isGrafanaAdmin *bool = nil
|
||||
if s.allowAssignGrafanaAdmin {
|
||||
isGrafanaAdmin = &grafanaAdmin
|
||||
}
|
||||
|
||||
return &BasicUserInfo{
|
||||
Id: claims.ID,
|
||||
Name: claims.Name,
|
||||
Email: email,
|
||||
Login: email,
|
||||
Role: role,
|
||||
Groups: groups,
|
||||
Id: claims.ID,
|
||||
Name: claims.Name,
|
||||
Email: email,
|
||||
Login: email,
|
||||
Role: string(role),
|
||||
IsGrafanaAdmin: isGrafanaAdmin,
|
||||
Groups: groups,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -120,18 +120,6 @@ func (s *SocialOkta) extractAPI(data *OktaUserInfoJson, client *http.Client) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SocialOkta) extractRole(data *OktaUserInfoJson) (string, error) {
|
||||
if s.roleAttributePath == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
role, err := s.searchJSONForStringAttr(s.roleAttributePath, data.rawJSON)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (s *SocialOkta) GetGroups(data *OktaUserInfoJson) []string {
|
||||
groups := make([]string, 0)
|
||||
if len(data.Groups) > 0 {
|
||||
|
||||
+96
-85
@@ -12,6 +12,8 @@ import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@@ -31,28 +33,29 @@ type SocialService struct {
|
||||
}
|
||||
|
||||
type OAuthInfo struct {
|
||||
ClientId, ClientSecret string
|
||||
Scopes []string
|
||||
AuthUrl, TokenUrl string
|
||||
Enabled bool
|
||||
EmailAttributeName string
|
||||
EmailAttributePath string
|
||||
RoleAttributePath string
|
||||
RoleAttributeStrict bool
|
||||
GroupsAttributePath string
|
||||
TeamIdsAttributePath string
|
||||
AllowedDomains []string
|
||||
HostedDomain string
|
||||
ApiUrl string
|
||||
TeamsUrl string
|
||||
AllowSignup bool
|
||||
Name string
|
||||
Icon string
|
||||
TlsClientCert string
|
||||
TlsClientKey string
|
||||
TlsClientCa string
|
||||
TlsSkipVerify bool
|
||||
UsePKCE bool
|
||||
ClientId, ClientSecret string
|
||||
Scopes []string
|
||||
AuthUrl, TokenUrl string
|
||||
Enabled bool
|
||||
EmailAttributeName string
|
||||
EmailAttributePath string
|
||||
RoleAttributePath string
|
||||
RoleAttributeStrict bool
|
||||
GroupsAttributePath string
|
||||
TeamIdsAttributePath string
|
||||
AllowedDomains []string
|
||||
AllowAssignGrafanaAdmin bool
|
||||
HostedDomain string
|
||||
ApiUrl string
|
||||
TeamsUrl string
|
||||
AllowSignup bool
|
||||
Name string
|
||||
Icon string
|
||||
TlsClientCert string
|
||||
TlsClientKey string
|
||||
TlsClientCa string
|
||||
TlsSkipVerify bool
|
||||
UsePKCE bool
|
||||
}
|
||||
|
||||
func ProvideService(cfg *setting.Cfg) *SocialService {
|
||||
@@ -66,30 +69,31 @@ 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(),
|
||||
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),
|
||||
Icon: sec.Key("icon").String(),
|
||||
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(),
|
||||
UsePKCE: sec.Key("use_pkce").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),
|
||||
Icon: sec.Key("icon").String(),
|
||||
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(),
|
||||
UsePKCE: sec.Key("use_pkce").MustBool(),
|
||||
AllowAssignGrafanaAdmin: sec.Key("allow_assign_grafana_admin").MustBool(false),
|
||||
}
|
||||
|
||||
// when empty_scopes parameter exists and is true, overwrite scope with empty value
|
||||
@@ -163,21 +167,17 @@ func ProvideService(cfg *setting.Cfg) *SocialService {
|
||||
// AzureAD.
|
||||
if name == "azuread" {
|
||||
ss.socialMap["azuread"] = &SocialAzureAD{
|
||||
SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole),
|
||||
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
|
||||
autoAssignOrgRole: cfg.AutoAssignOrgRole,
|
||||
roleAttributeStrict: info.RoleAttributeStrict,
|
||||
SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole),
|
||||
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
|
||||
}
|
||||
}
|
||||
|
||||
// Okta
|
||||
if name == "okta" {
|
||||
ss.socialMap["okta"] = &SocialOkta{
|
||||
SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole),
|
||||
apiUrl: info.ApiUrl,
|
||||
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
|
||||
roleAttributePath: info.RoleAttributePath,
|
||||
roleAttributeStrict: info.RoleAttributeStrict,
|
||||
SocialBase: newSocialBase(name, &config, info, cfg.AutoAssignOrgRole),
|
||||
apiUrl: info.ApiUrl,
|
||||
allowedGroups: util.SplitString(sec.Key("allowed_groups").String()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,8 +190,6 @@ func ProvideService(cfg *setting.Cfg) *SocialService {
|
||||
emailAttributeName: info.EmailAttributeName,
|
||||
emailAttributePath: info.EmailAttributePath,
|
||||
nameAttributePath: sec.Key("name_attribute_path").String(),
|
||||
roleAttributePath: info.RoleAttributePath,
|
||||
roleAttributeStrict: info.RoleAttributeStrict,
|
||||
groupsAttributePath: info.GroupsAttributePath,
|
||||
loginAttributePath: sec.Key("login_attribute_path").String(),
|
||||
idTokenAttributeName: sec.Key("id_token_attribute_name").String(),
|
||||
@@ -226,18 +224,18 @@ func ProvideService(cfg *setting.Cfg) *SocialService {
|
||||
}
|
||||
|
||||
type BasicUserInfo struct {
|
||||
Id string
|
||||
Name string
|
||||
Email string
|
||||
Login string
|
||||
Company string
|
||||
Role string
|
||||
Groups []string
|
||||
Id string
|
||||
Name string
|
||||
Email string
|
||||
Login string
|
||||
Role string
|
||||
IsGrafanaAdmin *bool // nil will avoid overriding user's set server admin setting
|
||||
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)
|
||||
return fmt.Sprintf("Id: %s, Name: %s, Email: %s, Login: %s, Role: %s, Groups: %v",
|
||||
b.Id, b.Name, b.Email, b.Login, b.Role, b.Groups)
|
||||
}
|
||||
|
||||
type SocialConnector interface {
|
||||
@@ -254,9 +252,10 @@ type SocialConnector interface {
|
||||
|
||||
type SocialBase struct {
|
||||
*oauth2.Config
|
||||
log log.Logger
|
||||
allowSignup bool
|
||||
allowedDomains []string
|
||||
log log.Logger
|
||||
allowSignup bool
|
||||
allowAssignGrafanaAdmin bool
|
||||
allowedDomains []string
|
||||
|
||||
roleAttributePath string
|
||||
roleAttributeStrict bool
|
||||
@@ -272,7 +271,8 @@ func (e Error) Error() string {
|
||||
}
|
||||
|
||||
const (
|
||||
grafanaCom = "grafana_com"
|
||||
grafanaCom = "grafana_com"
|
||||
RoleGrafanaAdmin = "GrafanaAdmin" // For AzureAD for example this value cannot contain spaces
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -297,13 +297,14 @@ func newSocialBase(name string,
|
||||
logger := log.New("oauth." + name)
|
||||
|
||||
return &SocialBase{
|
||||
Config: config,
|
||||
log: logger,
|
||||
allowSignup: info.AllowSignup,
|
||||
allowedDomains: info.AllowedDomains,
|
||||
autoAssignOrgRole: autoAssignOrgRole,
|
||||
roleAttributePath: info.RoleAttributePath,
|
||||
roleAttributeStrict: info.RoleAttributeStrict,
|
||||
Config: config,
|
||||
log: logger,
|
||||
allowSignup: info.AllowSignup,
|
||||
allowAssignGrafanaAdmin: info.AllowAssignGrafanaAdmin,
|
||||
allowedDomains: info.AllowedDomains,
|
||||
autoAssignOrgRole: autoAssignOrgRole,
|
||||
roleAttributePath: info.RoleAttributePath,
|
||||
roleAttributeStrict: info.RoleAttributeStrict,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,28 +312,38 @@ type groupStruct struct {
|
||||
Groups []string `json:"groups"`
|
||||
}
|
||||
|
||||
func (s *SocialBase) extractRole(rawJSON []byte, groups []string) (org.RoleType, error) {
|
||||
func (s *SocialBase) extractRoleAndAdmin(rawJSON []byte, groups []string) (org.RoleType, bool) {
|
||||
if s.roleAttributePath == "" {
|
||||
if s.autoAssignOrgRole != "" {
|
||||
return org.RoleType(s.autoAssignOrgRole), nil
|
||||
return org.RoleType(s.autoAssignOrgRole), false
|
||||
}
|
||||
|
||||
return "", nil
|
||||
return "", false
|
||||
}
|
||||
|
||||
role, err := s.searchJSONForStringAttr(s.roleAttributePath, rawJSON)
|
||||
if err == nil && role != "" {
|
||||
return org.RoleType(role), nil
|
||||
return getRoleFromSearch(role)
|
||||
}
|
||||
|
||||
if groupBytes, err := json.Marshal(groupStruct{groups}); err == nil {
|
||||
if role, err := s.searchJSONForStringAttr(
|
||||
s.roleAttributePath, groupBytes); err == nil && role != "" {
|
||||
return org.RoleType(role), nil
|
||||
role, err := s.searchJSONForStringAttr(s.roleAttributePath, groupBytes)
|
||||
if err == nil && role != "" {
|
||||
return getRoleFromSearch(role)
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
return "", false
|
||||
}
|
||||
|
||||
// match grafana admin role and translate to org role and bool.
|
||||
// treat the JSON search result to ensure correct casing.
|
||||
func getRoleFromSearch(role string) (org.RoleType, bool) {
|
||||
if strings.EqualFold(role, RoleGrafanaAdmin) {
|
||||
return org.RoleAdmin, true
|
||||
}
|
||||
|
||||
return org.RoleType(cases.Title(language.Und).String(role)), false
|
||||
}
|
||||
|
||||
// GetOAuthProviders returns available oauth providers and if they're enabled or not
|
||||
|
||||
Reference in New Issue
Block a user