Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
This commit is contained in:
@@ -245,78 +245,136 @@ func (s *SocialGenericOAuth) UserInfo(ctx context.Context, client *http.Client,
|
||||
defer s.reloadMutex.RUnlock()
|
||||
|
||||
s.log.Debug("Getting user info")
|
||||
toCheck := make([]*UserInfoJson, 0, 2)
|
||||
|
||||
if tokenData := s.extractFromToken(token); tokenData != nil {
|
||||
toCheck = append(toCheck, tokenData)
|
||||
// 1. Collect user info data from various sources
|
||||
dataSources := s.collectUserInfoData(ctx, client, token)
|
||||
|
||||
// 2. Build user info from collected data
|
||||
userInfo, externalOrgs, err := s.buildUserInfo(dataSources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. Post-process user info
|
||||
err = s.postProcessUserInfo(ctx, client, userInfo, externalOrgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 4. Validate user access
|
||||
err = s.validateUserAccess(ctx, client, userInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.log.Debug("User info result", "result", userInfo)
|
||||
return userInfo, nil
|
||||
}
|
||||
|
||||
// collectUserInfoData gathers user information from ID token, API, and access token
|
||||
func (s *SocialGenericOAuth) collectUserInfoData(ctx context.Context, client *http.Client, token *oauth2.Token) []*UserInfoJson {
|
||||
dataSources := make([]*UserInfoJson, 0, 3)
|
||||
|
||||
if idTokenData := s.extractFromIDToken(token); idTokenData != nil {
|
||||
dataSources = append(dataSources, idTokenData)
|
||||
}
|
||||
if apiData := s.extractFromAPI(ctx, client); apiData != nil {
|
||||
toCheck = append(toCheck, apiData)
|
||||
dataSources = append(dataSources, apiData)
|
||||
}
|
||||
if accessTokenData := s.extractFromAccessToken(token); accessTokenData != nil {
|
||||
dataSources = append(dataSources, accessTokenData)
|
||||
}
|
||||
|
||||
return dataSources
|
||||
}
|
||||
|
||||
// buildUserInfo constructs BasicUserInfo from collected data sources
|
||||
func (s *SocialGenericOAuth) buildUserInfo(dataSources []*UserInfoJson) (*social.BasicUserInfo, []string, error) {
|
||||
userInfo := &social.BasicUserInfo{}
|
||||
var externalOrgs []string
|
||||
for _, data := range toCheck {
|
||||
|
||||
for _, data := range dataSources {
|
||||
s.log.Debug("Processing external user info", "source", data.source, "data", data)
|
||||
|
||||
if userInfo.Id == "" {
|
||||
userInfo.Id = data.Sub
|
||||
s.extractBasicUserFields(userInfo, data)
|
||||
|
||||
if err := s.extractRoleAndOrgs(userInfo, &externalOrgs, data); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if userInfo.Name == "" {
|
||||
userInfo.Name = s.extractUserName(data)
|
||||
}
|
||||
s.extractUserGroups(userInfo, data)
|
||||
}
|
||||
|
||||
if userInfo.Login == "" {
|
||||
userInfo.Login = s.extractLogin(data)
|
||||
}
|
||||
return userInfo, externalOrgs, nil
|
||||
}
|
||||
|
||||
if userInfo.Email == "" {
|
||||
userInfo.Email = s.extractEmail(data)
|
||||
if userInfo.Email != "" {
|
||||
s.log.Debug("Set user info email from extracted email", "email", userInfo.Email)
|
||||
}
|
||||
}
|
||||
// extractBasicUserFields extracts basic user fields (ID, Name, Login, Email) from data
|
||||
func (s *SocialGenericOAuth) extractBasicUserFields(userInfo *social.BasicUserInfo, data *UserInfoJson) {
|
||||
if userInfo.Id == "" {
|
||||
userInfo.Id = data.Sub
|
||||
}
|
||||
|
||||
if userInfo.Role == "" && !s.info.SkipOrgRoleSync {
|
||||
role, grafanaAdmin, err := s.extractRoleAndAdminOptional(data.rawJSON, []string{})
|
||||
if err != nil {
|
||||
s.log.Warn("Failed to extract role", "err", err)
|
||||
} else {
|
||||
userInfo.Role = role
|
||||
if s.info.AllowAssignGrafanaAdmin {
|
||||
userInfo.IsGrafanaAdmin = &grafanaAdmin
|
||||
}
|
||||
}
|
||||
}
|
||||
if userInfo.Name == "" {
|
||||
userInfo.Name = s.extractUserName(data)
|
||||
}
|
||||
|
||||
if len(externalOrgs) == 0 && !s.info.SkipOrgRoleSync {
|
||||
var err error
|
||||
externalOrgs, err = s.extractOrgs(data.rawJSON)
|
||||
if err != nil {
|
||||
s.log.Warn("Failed to extract orgs", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if userInfo.Login == "" {
|
||||
userInfo.Login = s.extractLogin(data)
|
||||
}
|
||||
|
||||
if len(userInfo.Groups) == 0 {
|
||||
groups, err := s.extractGroups(data)
|
||||
if err != nil {
|
||||
s.log.Warn("Failed to extract groups", "err", err)
|
||||
} else if len(groups) > 0 {
|
||||
s.log.Debug("Setting user info groups from extracted groups")
|
||||
userInfo.Groups = groups
|
||||
if userInfo.Email == "" {
|
||||
userInfo.Email = s.extractEmail(data)
|
||||
if userInfo.Email != "" {
|
||||
s.log.Debug("Set user info email from extracted email", "email", userInfo.Email)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extractRoleAndOrgs extracts role and organization information from data
|
||||
func (s *SocialGenericOAuth) extractRoleAndOrgs(userInfo *social.BasicUserInfo, externalOrgs *[]string, data *UserInfoJson) error {
|
||||
if userInfo.Role == "" && !s.info.SkipOrgRoleSync {
|
||||
role, grafanaAdmin, err := s.extractRoleAndAdminOptional(data.rawJSON, []string{})
|
||||
if err != nil {
|
||||
s.log.Warn("Failed to extract role", "err", err)
|
||||
} else {
|
||||
userInfo.Role = role
|
||||
if s.info.AllowAssignGrafanaAdmin {
|
||||
userInfo.IsGrafanaAdmin = &grafanaAdmin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(*externalOrgs) == 0 && !s.info.SkipOrgRoleSync {
|
||||
orgs, err := s.extractOrgs(data.rawJSON)
|
||||
if err != nil {
|
||||
s.log.Warn("Failed to extract orgs", "err", err)
|
||||
return err
|
||||
}
|
||||
*externalOrgs = orgs
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractUserGroups extracts group information from data
|
||||
func (s *SocialGenericOAuth) extractUserGroups(userInfo *social.BasicUserInfo, data *UserInfoJson) {
|
||||
if len(userInfo.Groups) == 0 {
|
||||
groups, err := s.extractGroups(data)
|
||||
if err != nil {
|
||||
s.log.Warn("Failed to extract groups", "err", err)
|
||||
} else if len(groups) > 0 {
|
||||
s.log.Debug("Setting user info groups from extracted groups")
|
||||
userInfo.Groups = groups
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// postProcessUserInfo handles post-processing of user info (org roles, private email, etc.)
|
||||
func (s *SocialGenericOAuth) postProcessUserInfo(ctx context.Context, client *http.Client, userInfo *social.BasicUserInfo, externalOrgs []string) error {
|
||||
if !s.info.SkipOrgRoleSync {
|
||||
userInfo.OrgRoles = s.orgRoleMapper.MapOrgRoles(s.orgMappingCfg, externalOrgs, userInfo.Role)
|
||||
if s.info.RoleAttributeStrict && len(userInfo.OrgRoles) == 0 {
|
||||
// If no roles are found and role_attribute_strict is set, return an error.
|
||||
// The s.info.RoleAttributeStrict is necessary, because there is a case when len(userInfo.OrgRoles) == 0,
|
||||
// but strict role mapping is not enabled (when getAllOrgs fails).
|
||||
return nil, errRoleAttributeStrictViolation.Errorf("could not evaluate any valid roles using IdP provided data")
|
||||
return errRoleAttributeStrictViolation.Errorf("could not evaluate any valid roles using IdP provided data")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,11 +383,11 @@ func (s *SocialGenericOAuth) UserInfo(ctx context.Context, client *http.Client,
|
||||
}
|
||||
|
||||
if s.canFetchPrivateEmail(userInfo) {
|
||||
var err error
|
||||
userInfo.Email, err = s.fetchPrivateEmail(ctx, client)
|
||||
email, err := s.fetchPrivateEmail(ctx, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
userInfo.Email = email
|
||||
s.log.Debug("Setting email from fetched private email", "email", userInfo.Email)
|
||||
}
|
||||
|
||||
@@ -338,28 +396,32 @@ func (s *SocialGenericOAuth) UserInfo(ctx context.Context, client *http.Client,
|
||||
userInfo.Login = userInfo.Email
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateUserAccess validates user access based on team, organization, and group membership
|
||||
func (s *SocialGenericOAuth) validateUserAccess(ctx context.Context, client *http.Client, userInfo *social.BasicUserInfo) error {
|
||||
if !s.isTeamMember(ctx, client) {
|
||||
return nil, &SocialError{"User not a member of one of the required teams"}
|
||||
return &SocialError{"User not a member of one of the required teams"}
|
||||
}
|
||||
|
||||
if !s.isOrganizationMember(ctx, client) {
|
||||
return nil, &SocialError{"User not a member of one of the required organizations"}
|
||||
return &SocialError{"User not a member of one of the required organizations"}
|
||||
}
|
||||
|
||||
if !s.isGroupMember(userInfo.Groups) {
|
||||
return nil, errMissingGroupMembership
|
||||
return errMissingGroupMembership
|
||||
}
|
||||
|
||||
s.log.Debug("User info result", "result", userInfo)
|
||||
return userInfo, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SocialGenericOAuth) canFetchPrivateEmail(userinfo *social.BasicUserInfo) bool {
|
||||
return s.info.ApiUrl != "" && userinfo.Email == ""
|
||||
}
|
||||
|
||||
func (s *SocialGenericOAuth) extractFromToken(token *oauth2.Token) *UserInfoJson {
|
||||
s.log.Debug("Extracting user info from OAuth token")
|
||||
func (s *SocialGenericOAuth) extractFromIDToken(token *oauth2.Token) *UserInfoJson {
|
||||
s.log.Debug("Extracting user info from OAuth ID token")
|
||||
|
||||
idTokenAttribute := "id_token"
|
||||
if s.idTokenAttributeName != "" {
|
||||
@@ -373,21 +435,44 @@ func (s *SocialGenericOAuth) extractFromToken(token *oauth2.Token) *UserInfoJson
|
||||
return nil
|
||||
}
|
||||
|
||||
rawJSON, err := s.retrieveRawIDToken(idToken)
|
||||
rawJSON, err := s.retrieveRawJWTPayload(idToken)
|
||||
if err != nil {
|
||||
s.log.Warn("Error retrieving id_token", "error", err, "token", fmt.Sprintf("%+v", token))
|
||||
s.log.Warn("Error retrieving id_token payload", "error", err, "token", fmt.Sprintf("%+v", token))
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.parseUserInfoFromJSON(rawJSON, "id_token")
|
||||
}
|
||||
|
||||
func (s *SocialGenericOAuth) extractFromAccessToken(token *oauth2.Token) *UserInfoJson {
|
||||
s.log.Debug("Extracting user info from OAuth access token")
|
||||
|
||||
accessToken := token.AccessToken
|
||||
if accessToken == "" {
|
||||
s.log.Debug("No access token found")
|
||||
return nil
|
||||
}
|
||||
|
||||
rawJSON, err := s.retrieveRawJWTPayload(accessToken)
|
||||
if err != nil {
|
||||
s.log.Warn("Error retrieving access token payload", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return s.parseUserInfoFromJSON(rawJSON, "access_token")
|
||||
}
|
||||
|
||||
// parseUserInfoFromJSON is a helper method to parse UserInfoJson from raw JSON and source
|
||||
func (s *SocialGenericOAuth) parseUserInfoFromJSON(rawJSON []byte, source string) *UserInfoJson {
|
||||
var data UserInfoJson
|
||||
if err := json.Unmarshal(rawJSON, &data); err != nil {
|
||||
s.log.Error("Error decoding id_token JSON", "raw_json", string(rawJSON), "error", err)
|
||||
s.log.Error("Error decoding user info JSON", "raw_json", string(rawJSON), "error", err, "source", source)
|
||||
return nil
|
||||
}
|
||||
|
||||
data.rawJSON = rawJSON
|
||||
data.source = "token"
|
||||
s.log.Debug("Received id_token", "raw_json", string(data.rawJSON), "data", data.String())
|
||||
data.source = source
|
||||
s.log.Debug("Parsed user info from JSON", "raw_json", string(rawJSON), "data", data.String(), "source", source)
|
||||
return &data
|
||||
}
|
||||
|
||||
@@ -404,18 +489,7 @@ func (s *SocialGenericOAuth) extractFromAPI(ctx context.Context, client *http.Cl
|
||||
return nil
|
||||
}
|
||||
|
||||
rawJSON := rawUserInfoResponse.Body
|
||||
|
||||
var data UserInfoJson
|
||||
if err := json.Unmarshal(rawJSON, &data); err != nil {
|
||||
s.log.Error("Error decoding user info response", "raw_json", rawJSON, "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
data.rawJSON = rawJSON
|
||||
data.source = "API"
|
||||
s.log.Debug("Received user info response from API", "raw_json", string(rawJSON), "data", data.String())
|
||||
return &data
|
||||
return s.parseUserInfoFromJSON(rawUserInfoResponse.Body, "API")
|
||||
}
|
||||
|
||||
func (s *SocialGenericOAuth) extractEmail(data *UserInfoJson) string {
|
||||
|
||||
@@ -31,6 +31,7 @@ func TestUserInfoSearchesForEmailAndOrgRoles(t *testing.T) {
|
||||
AllowAssignGrafanaAdmin bool
|
||||
ResponseBody any
|
||||
OAuth2Extra any
|
||||
AccessToken string
|
||||
Setup func(*orgtest.FakeOrgService)
|
||||
RoleAttributePath string
|
||||
RoleAttributeStrict bool
|
||||
@@ -440,6 +441,62 @@ func TestUserInfoSearchesForEmailAndOrgRoles(t *testing.T) {
|
||||
ExpectedEmail: "john.doe@example.com",
|
||||
ExpectedOrgRoles: map[int64]org.RoleType{2: org.RoleViewer},
|
||||
},
|
||||
// Access Token Test Cases
|
||||
{
|
||||
Name: "Given a valid access token with role, no ID token, no API response, use access token",
|
||||
ResponseBody: map[string]any{},
|
||||
OAuth2Extra: map[string]any{},
|
||||
AccessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiRWRpdG9yIiwiZW1haWwiOiJhY2Nlc3MudG9rZW5AZXhhbXBsZS5jb20ifQ.oVEMSJVqBwrGXOcwGgXL_8J-CZhgFVPjXXSqzPJQ5JU", // { "role": "Editor", "email": "access.token@example.com" }
|
||||
RoleAttributePath: "role",
|
||||
ExpectedEmail: "access.token@example.com",
|
||||
ExpectedOrgRoles: map[int64]org.RoleType{2: org.RoleEditor},
|
||||
},
|
||||
{
|
||||
Name: "Given a valid access token with org roles, no ID token, no API response, use access token",
|
||||
ResponseBody: map[string]any{},
|
||||
OAuth2Extra: map[string]any{},
|
||||
AccessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiVmlld2VyIiwiZW1haWwiOiJhY2Nlc3MudG9rZW5AZXhhbXBsZS5jb20iLCJpbmZvIjp7InJvbGVzIjpbImFjY2Vzcy1kZXYiLCJhY2Nlc3Mtb3BzIl19fQ.g8-mNJQDL9CJWgRTFdKBRRKbsHZfFhJrzPYQGXfxGIE", // { "role": "Viewer", "email": "access.token@example.com", "info": { "roles": [ "access-dev", "access-ops" ] }}
|
||||
RoleAttributePath: "role",
|
||||
OrgAttributePath: "info.roles",
|
||||
OrgMapping: []string{"access-dev:org_dev:Admin", "access-ops:org_engineering:Editor"},
|
||||
ExpectedEmail: "access.token@example.com",
|
||||
ExpectedOrgRoles: map[int64]org.RoleType{4: org.RoleAdmin, 5: org.RoleEditor},
|
||||
},
|
||||
{
|
||||
Name: "Given a valid access token and ID token, prefer ID token",
|
||||
ResponseBody: map[string]any{},
|
||||
OAuth2Extra: map[string]any{
|
||||
// { "role": "Admin", "email": "id.token@example.com" }
|
||||
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiQWRtaW4iLCJlbWFpbCI6ImlkLnRva2VuQGV4YW1wbGUuY29tIn0.T8wcoOOPQ_av9VsOFoYJZGNFGJgG0d3LPDvtxvgODkU",
|
||||
},
|
||||
AccessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiRWRpdG9yIiwiZW1haWwiOiJhY2Nlc3MudG9rZW5AZXhhbXBsZS5jb20ifQ.oVEMSJVqBwrGXOcwGgXL_8J-CZhgFVPjXXSqzPJQ5JU", // { "role": "Editor", "email": "access.token@example.com" }
|
||||
RoleAttributePath: "role",
|
||||
ExpectedEmail: "id.token@example.com",
|
||||
ExpectedOrgRoles: map[int64]org.RoleType{2: org.RoleAdmin},
|
||||
},
|
||||
{
|
||||
Name: "Given a valid access token with no email, ID token with no role, API response with no data, merge",
|
||||
ResponseBody: map[string]any{},
|
||||
OAuth2Extra: map[string]any{
|
||||
// { "email": "id.token@example.com" }
|
||||
"id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImlkLnRva2VuQGV4YW1wbGUuY29tIn0.k5GwPcZvGe2BE_jgwN0ntz0nz4KlYhEd0hRRLApkTJ4",
|
||||
},
|
||||
AccessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiRWRpdG9yIn0.gfnKWZKNFNqrILhHFzabBVEWnJJIZBmQSBwLPCHhLUY", // { "role": "Editor" }
|
||||
RoleAttributePath: "role",
|
||||
ExpectedEmail: "id.token@example.com",
|
||||
ExpectedOrgRoles: map[int64]org.RoleType{2: org.RoleEditor},
|
||||
},
|
||||
{
|
||||
Name: "Given a valid access token with GrafanaAdmin role and AssignGrafanaAdmin enabled",
|
||||
AllowAssignGrafanaAdmin: true,
|
||||
ResponseBody: map[string]any{},
|
||||
OAuth2Extra: map[string]any{},
|
||||
AccessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiR3JhZmFuYUFkbWluIiwiZW1haWwiOiJhY2Nlc3MudG9rZW5AZXhhbXBsZS5jb20ifQ.fJPjMgZW9bOYXOLgOUekNQmNrVbUNhU1iqQJwqFWzUY", // { "role": "GrafanaAdmin", "email": "access.token@example.com" }
|
||||
RoleAttributePath: "role",
|
||||
ExpectedEmail: "access.token@example.com",
|
||||
ExpectedGrafanaAdmin: trueBoolPtr(),
|
||||
ExpectedOrgRoles: map[int64]org.RoleType{2: org.RoleAdmin},
|
||||
},
|
||||
}
|
||||
|
||||
cfg := &setting.Cfg{
|
||||
@@ -479,8 +536,9 @@ func TestUserInfoSearchesForEmailAndOrgRoles(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
provider.info.ApiUrl = ts.URL
|
||||
|
||||
staticToken := oauth2.Token{
|
||||
AccessToken: "",
|
||||
AccessToken: tc.AccessToken,
|
||||
TokenType: "",
|
||||
RefreshToken: "",
|
||||
Expiry: time.Now(),
|
||||
@@ -853,7 +911,7 @@ func TestPayloadCompression(t *testing.T) {
|
||||
}
|
||||
|
||||
token := staticToken.WithExtra(test.OAuth2Extra)
|
||||
userInfo := provider.extractFromToken(token)
|
||||
userInfo := provider.extractFromIDToken(token)
|
||||
|
||||
if test.ExpectedEmail == "" {
|
||||
require.Nil(t, userInfo, "Testing case %q", test.Name)
|
||||
|
||||
@@ -275,7 +275,7 @@ func (s *SocialGitlab) extractFromToken(ctx context.Context, client *http.Client
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rawJSON, err := s.retrieveRawIDToken(idToken)
|
||||
rawJSON, err := s.retrieveRawJWTPayload(idToken)
|
||||
if err != nil {
|
||||
s.log.Warn("Error retrieving id_token", "error", err, "token", fmt.Sprintf("%+v", idToken))
|
||||
return nil, nil
|
||||
|
||||
@@ -236,7 +236,7 @@ func (s *SocialGoogle) extractFromToken(_ context.Context, _ *http.Client, token
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
rawJSON, err := s.retrieveRawIDToken(idToken)
|
||||
rawJSON, err := s.retrieveRawJWTPayload(idToken)
|
||||
if err != nil {
|
||||
s.log.Warn("Error retrieving id_token", "error", err, "token", fmt.Sprintf("%+v", idToken))
|
||||
return nil, nil
|
||||
|
||||
@@ -196,21 +196,21 @@ func (s *SocialBase) isGroupMember(groups []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *SocialBase) retrieveRawIDToken(idToken any) ([]byte, error) {
|
||||
tokenString, ok := idToken.(string)
|
||||
func (s *SocialBase) retrieveRawJWTPayload(token any) ([]byte, error) {
|
||||
tokenString, ok := token.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("id_token is not a string: %v", idToken)
|
||||
return nil, fmt.Errorf("token is not a string: %v", token)
|
||||
}
|
||||
|
||||
jwtRegexp := regexp.MustCompile("^([-_a-zA-Z0-9=]+)[.]([-_a-zA-Z0-9=]+)[.]([-_a-zA-Z0-9=]+)$")
|
||||
matched := jwtRegexp.FindStringSubmatch(tokenString)
|
||||
if matched == nil {
|
||||
return nil, fmt.Errorf("id_token is not in JWT format: %s", tokenString)
|
||||
return nil, fmt.Errorf("token is not in JWT format: %s", tokenString)
|
||||
}
|
||||
|
||||
rawJSON, err := base64.RawURLEncoding.DecodeString(matched[2])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error base64 decoding id_token: %w", err)
|
||||
return nil, fmt.Errorf("error base64 decoding token payload: %w", err)
|
||||
}
|
||||
|
||||
headerBytes, err := base64.RawURLEncoding.DecodeString(matched[1])
|
||||
|
||||
@@ -114,9 +114,6 @@ func (s *JWT) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identi
|
||||
|
||||
if !s.cfg.JWTAuth.SkipOrgRoleSync {
|
||||
role, grafanaAdmin := s.extractRoleAndAdmin(claims)
|
||||
if err != nil {
|
||||
s.log.Warn("Failed to extract role", "err", err)
|
||||
}
|
||||
|
||||
if s.cfg.JWTAuth.AllowAssignGrafanaAdmin {
|
||||
id.IsGrafanaAdmin = &grafanaAdmin
|
||||
|
||||
@@ -861,7 +861,7 @@ func TestIntegrationFolderGetPermissions(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestFoldersCreateAPIEndpointK8S is the counterpart of pkg/api/folder_test.go TestFoldersCreateAPIEndpoint
|
||||
func TestFoldersCreateAPIEndpointK8S(t *testing.T) {
|
||||
func TestIntegrationFoldersCreateAPIEndpointK8S(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
@@ -902,7 +902,7 @@ func TestFoldersCreateAPIEndpointK8S(t *testing.T) {
|
||||
description: "folder creation fails without permissions to create a folder",
|
||||
input: folderWithoutParentInput,
|
||||
expectedCode: http.StatusForbidden,
|
||||
expectedMessage: dashboards.ErrFolderAccessDenied.Error(),
|
||||
expectedMessage: fmt.Sprintf("You'll need additional permissions to perform this action. Permissions needed: %s", "folders:create"),
|
||||
permissions: []resourcepermissions.SetResourcePermissionCommand{},
|
||||
},
|
||||
{
|
||||
@@ -1022,7 +1022,7 @@ func testDescription(description string, expectedErr error) string {
|
||||
}
|
||||
|
||||
// There are no counterpart of TestFoldersGetAPIEndpointK8S in pkg/api/folder_test.go
|
||||
func TestFoldersGetAPIEndpointK8S(t *testing.T) {
|
||||
func TestIntegrationFoldersGetAPIEndpointK8S(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
@@ -1062,6 +1062,7 @@ func TestFoldersGetAPIEndpointK8S(t *testing.T) {
|
||||
expectedOutput: []dtos.FolderSearchHit{
|
||||
{UID: "foo", Title: "Folder 1"},
|
||||
{UID: "qux", Title: "Folder 3"},
|
||||
{UID: folder.SharedWithMeFolder.UID, Title: folder.SharedWithMeFolder.Title},
|
||||
},
|
||||
permissions: folderReadAndCreatePermission,
|
||||
},
|
||||
@@ -1107,7 +1108,7 @@ func TestFoldersGetAPIEndpointK8S(t *testing.T) {
|
||||
}
|
||||
|
||||
// test on all dualwriter modes
|
||||
for mode := 1; mode <= 4; mode++ {
|
||||
for mode := 0; mode <= 4; mode++ {
|
||||
for _, tc := range tcs {
|
||||
t.Run(fmt.Sprintf("Mode: %d, %s", mode, tc.description), func(t *testing.T) {
|
||||
modeDw := grafanarest.DualWriterMode(mode)
|
||||
@@ -1123,6 +1124,7 @@ func TestFoldersGetAPIEndpointK8S(t *testing.T) {
|
||||
},
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagNestedFolders,
|
||||
featuremgmt.FlagUnifiedStorageSearch,
|
||||
featuremgmt.FlagKubernetesClientDashboardsFolders,
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user