[release-11.6.4] Auth: fix role_attribute_path when using ID tokens in Gitlab OAuth (#107655)

Auth: fix role_attribute_path when using ID tokens in Gitlab OAuth (#107634)

(cherry picked from commit 5b5d5fe675)
This commit is contained in:
grafana-delivery-bot[bot]
2025-07-07 08:16:47 +02:00
committed by GitHub
parent 9df3093561
commit 3a4bda5604
2 changed files with 36 additions and 1 deletions
@@ -304,6 +304,8 @@ func (s *SocialGitlab) extractFromToken(ctx context.Context, client *http.Client
data.Groups = userInfo.Groups
}
data.raw = rawJSON
s.log.Debug("Resolved user data", "data", fmt.Sprintf("%+v", data))
return &data, nil
}
@@ -37,6 +37,8 @@ const (
rootUserRespBody = `{"id":1,"username":"root","name":"Administrator","state":"active","email":"root@example.org", "confirmed_at":"2022-09-13T19:38:04.891Z","is_admin":true,"namespace_id":1}`
editorUserRespBody = `{"id":3,"username":"gitlab-editor","name":"Gitlab Editor","state":"active","email":"gitlab-editor@example.org", "confirmed_at":"2022-09-13T19:38:04.891Z","is_admin":false,"namespace_id":1}`
editorUserIDToken = `{"sub":"3","preferred_username":"gitlab-editor","name":"Gitlab Editor","email":"gitlab-editor@example.org","email_verified":true,"groups_direct":["editors", "viewers"]}` // #nosec G101 not a hardcoded credential
adminGroup = `{"id":4,"web_url":"http://grafana-gitlab.local/groups/admins","name":"Admins","path":"admins","project_creation_level":"developer","full_name":"Admins","full_path":"admins","created_at":"2022-09-13T19:38:04.891Z"}`
editorGroup = `{"id":5,"web_url":"http://grafana-gitlab.local/groups/editors","name":"Editors","path":"editors","project_creation_level":"developer","full_name":"Editors","full_path":"editors","created_at":"2022-09-13T19:38:15.074Z"}`
viewerGroup = `{"id":6,"web_url":"http://grafana-gitlab.local/groups/viewers","name":"Viewers","path":"viewers","project_creation_level":"developer","full_name":"Viewers","full_path":"viewers","created_at":"2022-09-13T19:38:25.777Z"}`
@@ -61,6 +63,7 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
GroupsRespBody string
GroupHeaders map[string]string
RoleAttributePath string
IDToken string
ExpectedLogin string
ExpectedEmail string
ExpectedRoles map[int64]org.RoleType
@@ -180,6 +183,24 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
ExpectedEmail: "gitlab-editor@example.org",
ExpectedRoles: map[int64]org.RoleType{4: "Editor", 5: "Viewer"},
},
{
Name: "Maps roles from ID token attributes if available",
RoleAttributePath: `email=='gitlab-editor@example.org' && 'Editor' || 'Viewer'`,
IDToken: editorUserIDToken,
ExpectedLogin: "gitlab-editor",
ExpectedEmail: "gitlab-editor@example.org",
ExpectedRoles: map[int64]org.RoleType{1: "Editor"},
ExpectedGrafanaAdmin: nilPointer,
},
{
Name: "Maps groups from ID token groups if available",
RoleAttributePath: gitlabAttrPath,
IDToken: editorUserIDToken,
ExpectedLogin: "gitlab-editor",
ExpectedEmail: "gitlab-editor@example.org",
ExpectedRoles: map[int64]org.RoleType{1: "Editor"},
ExpectedGrafanaAdmin: nilPointer,
},
{
Name: "Should return error when neither role attribute path nor org mapping evaluates to a role and role attribute strict is enabled",
Cfg: conf{RoleAttributeStrict: true, OrgMapping: []string{"other:Org4:Editor"}},
@@ -230,8 +251,17 @@ func TestSocialGitlab_UserInfo(t *testing.T) {
require.Fail(t, "unexpected request URI: "+r.RequestURI)
}
}))
token := &oauth2.Token{}
if tt.IDToken != "" {
emptyJWTHeader := base64.RawURLEncoding.EncodeToString([]byte("{}"))
JWTBody := base64.RawURLEncoding.EncodeToString([]byte(tt.IDToken))
idToken := fmt.Sprintf("%s.%s.signature", emptyJWTHeader, JWTBody)
token = token.WithExtra(map[string]any{"id_token": idToken})
}
provider.info.ApiUrl = ts.URL + apiURI
actualResult, err := provider.UserInfo(context.Background(), ts.Client(), &oauth2.Token{})
actualResult, err := provider.UserInfo(context.Background(), ts.Client(), token)
if tt.ExpectedError != nil {
require.ErrorIs(t, err, tt.ExpectedError)
return
@@ -382,6 +412,9 @@ func TestSocialGitlab_extractFromToken(t *testing.T) {
}
for _, tc := range testCases {
if tc.wantUser != nil {
tc.wantUser.raw = []byte(tc.payload)
}
t.Run(tc.name, func(t *testing.T) {
// Create a test client with a dummy token
client := oauth2.NewClient(context.Background(), &tokenSource{accessToken: "dummy_access_token"})