From 7dbad2715662d26c56cc062d48632bdb4fd03038 Mon Sep 17 00:00:00 2001 From: Marcell Mars Date: Wed, 7 Aug 2024 09:21:28 +0200 Subject: [PATCH] id_token & userinfo endpoint's public groups check - if `groups` scope provided it checks if all, r:org or r:admin are provided to pass all the groups. otherwise only public memberships - in InfoOAuth it captures scopes from the token if provided in the header. the extraction from the header is maybe a candidate for the separate function so no duplicated code --- routers/web/auth/oauth.go | 41 +++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index c9cdb08d9f..0626157dd8 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -244,7 +244,9 @@ func newAccessTokenResponse(ctx go_context.Context, grant *auth.OAuth2Grant, ser idToken.EmailVerified = user.IsActive } if grant.ScopeContains("groups") { - groups, err := getOAuthGroupsForUser(ctx, user) + onlyPublicGroups := ifOnlyPublicGroups(grant.Scope) + + groups, err := getOAuthGroupsForUser(ctx, user, onlyPublicGroups) if err != nil { log.Error("Error getting groups: %v", err) return nil, &AccessTokenError{ @@ -279,7 +281,18 @@ type userInfoResponse struct { Username string `json:"preferred_username"` Email string `json:"email"` Picture string `json:"picture"` - Groups []string `json:"groups"` + Groups []string `json:"groups,omitempty"` +} + +func ifOnlyPublicGroups(scopes string) bool { + scopes = strings.ReplaceAll(scopes, ",", " ") + scopesList := strings.Fields(scopes) + for _, scope := range scopesList { + if scope == "all" || scope == "read:organization" || scope == "read:admin" { + return false + } + } + return true } // InfoOAuth manages request for userinfo endpoint @@ -298,7 +311,18 @@ func InfoOAuth(ctx *context.Context) { Picture: ctx.Doer.AvatarLink(ctx), } - groups, err := getOAuthGroupsForUser(ctx, ctx.Doer) + var token string + if auHead := ctx.Req.Header.Get("Authorization"); auHead != "" { + auths := strings.Fields(auHead) + if len(auths) == 2 && (auths[0] == "token" || strings.ToLower(auths[0]) == "bearer") { + token = auths[1] + } + } + + _, grantScopes := auth_service.CheckOAuthAccessToken(ctx, token) + onlyPublicGroups := ifOnlyPublicGroups(grantScopes) + + groups, err := getOAuthGroupsForUser(ctx, ctx.Doer, onlyPublicGroups) if err != nil { ctx.ServerError("Oauth groups for user", err) return @@ -310,7 +334,7 @@ func InfoOAuth(ctx *context.Context) { // returns a list of "org" and "org:team" strings, // that the given user is a part of. -func getOAuthGroupsForUser(ctx go_context.Context, user *user_model.User) ([]string, error) { +func getOAuthGroupsForUser(ctx go_context.Context, user *user_model.User, onlyPublicGroups bool) ([]string, error) { orgs, err := org_model.GetUserOrgsList(ctx, user) if err != nil { return nil, fmt.Errorf("GetUserOrgList: %w", err) @@ -318,6 +342,15 @@ func getOAuthGroupsForUser(ctx go_context.Context, user *user_model.User) ([]str var groups []string for _, org := range orgs { + if setting.OAuth2.EnableAdditionalGrantScopes { + if onlyPublicGroups { + public, err := org_model.IsPublicMembership(ctx, org.ID, user.ID) + if !public && err == nil { + continue + } + } + } + groups = append(groups, org.Name) teams, err := org.LoadTeams(ctx) if err != nil {