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
This commit is contained in:
Marcell Mars 2024-08-07 09:21:28 +02:00
parent 4eb8d8c496
commit 7dbad27156

View file

@ -244,7 +244,9 @@ func newAccessTokenResponse(ctx go_context.Context, grant *auth.OAuth2Grant, ser
idToken.EmailVerified = user.IsActive idToken.EmailVerified = user.IsActive
} }
if grant.ScopeContains("groups") { if grant.ScopeContains("groups") {
groups, err := getOAuthGroupsForUser(ctx, user) onlyPublicGroups := ifOnlyPublicGroups(grant.Scope)
groups, err := getOAuthGroupsForUser(ctx, user, onlyPublicGroups)
if err != nil { if err != nil {
log.Error("Error getting groups: %v", err) log.Error("Error getting groups: %v", err)
return nil, &AccessTokenError{ return nil, &AccessTokenError{
@ -279,7 +281,18 @@ type userInfoResponse struct {
Username string `json:"preferred_username"` Username string `json:"preferred_username"`
Email string `json:"email"` Email string `json:"email"`
Picture string `json:"picture"` 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 // InfoOAuth manages request for userinfo endpoint
@ -298,7 +311,18 @@ func InfoOAuth(ctx *context.Context) {
Picture: ctx.Doer.AvatarLink(ctx), 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 { if err != nil {
ctx.ServerError("Oauth groups for user", err) ctx.ServerError("Oauth groups for user", err)
return return
@ -310,7 +334,7 @@ func InfoOAuth(ctx *context.Context) {
// returns a list of "org" and "org:team" strings, // returns a list of "org" and "org:team" strings,
// that the given user is a part of. // 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) orgs, err := org_model.GetUserOrgsList(ctx, user)
if err != nil { if err != nil {
return nil, fmt.Errorf("GetUserOrgList: %w", err) 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 var groups []string
for _, org := range orgs { 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) groups = append(groups, org.Name)
teams, err := org.LoadTeams(ctx) teams, err := org.LoadTeams(ctx)
if err != nil { if err != nil {