Browse Source

Add groups scope/claim to OIDC/OAuth2 Provider (#17367)

* Add groups scope/claim to OICD/OAuth2

Add support for groups claim as part of the OIDC/OAuth2 flow.
Groups is a list of "org" and "org:team" strings to allow clients to
authorize based on the groups a user is part of.

Signed-off-by: Nico Schieder <code@nico-schieder.de>
Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
tags/v1.16.0-rc1
Nico Schieder 2 years ago
parent
commit
870f5fbc41
No account linked to committer's email address

+ 50
- 5
routers/web/user/oauth.go View File

@@ -207,6 +207,17 @@ func newAccessTokenResponse(grant *login.OAuth2Grant, serverKey, clientKey oauth
idToken.Email = user.Email
idToken.EmailVerified = user.IsActive
}
if grant.ScopeContains("groups") {
groups, err := getOAuthGroupsForUser(user)
if err != nil {
log.Error("Error getting groups: %v", err)
return nil, &AccessTokenError{
ErrorCode: AccessTokenErrorCodeInvalidRequest,
ErrorDescription: "server error",
}
}
idToken.Groups = groups
}

signedIDToken, err = idToken.SignToken(clientKey)
if err != nil {
@@ -227,11 +238,12 @@ func newAccessTokenResponse(grant *login.OAuth2Grant, serverKey, clientKey oauth
}

type userInfoResponse struct {
Sub string `json:"sub"`
Name string `json:"name"`
Username string `json:"preferred_username"`
Email string `json:"email"`
Picture string `json:"picture"`
Sub string `json:"sub"`
Name string `json:"name"`
Username string `json:"preferred_username"`
Email string `json:"email"`
Picture string `json:"picture"`
Groups []string `json:"groups"`
}

// InfoOAuth manages request for userinfo endpoint
@@ -241,6 +253,7 @@ func InfoOAuth(ctx *context.Context) {
ctx.HandleText(http.StatusUnauthorized, "no valid authorization")
return
}

response := &userInfoResponse{
Sub: fmt.Sprint(ctx.User.ID),
Name: ctx.User.FullName,
@@ -248,9 +261,41 @@ func InfoOAuth(ctx *context.Context) {
Email: ctx.User.Email,
Picture: ctx.User.AvatarLink(),
}

groups, err := getOAuthGroupsForUser(ctx.User)
if err != nil {
ctx.ServerError("Oauth groups for user", err)
return
}
response.Groups = groups

ctx.JSON(http.StatusOK, response)
}

// returns a list of "org" and "org:team" strings,
// that the given user is a part of.
func getOAuthGroupsForUser(user *models.User) ([]string, error) {
orgs, err := models.GetUserOrgsList(user)
if err != nil {
return nil, fmt.Errorf("GetUserOrgList: %v", err)
}

var groups []string
for _, org := range orgs {
groups = append(groups, org.Name)

if err := org.LoadTeams(); err != nil {
return nil, fmt.Errorf("LoadTeams: %v", err)
}
for _, team := range org.Teams {
if team.IsMember(user.ID) {
groups = append(groups, org.Name+":"+team.LowerName)
}
}
}
return groups, nil
}

// IntrospectOAuth introspects an oauth token
func IntrospectOAuth(ctx *context.Context) {
if ctx.User == nil {

+ 3
- 0
services/auth/source/oauth2/token.go View File

@@ -83,6 +83,9 @@ type OIDCToken struct {
// Scope email
Email string `json:"email,omitempty"`
EmailVerified bool `json:"email_verified,omitempty"`

// Groups are generated by organization and team names
Groups []string `json:"groups,omitempty"`
}

// SignToken signs an id_token with the (symmetric) client secret key

+ 4
- 2
templates/user/auth/oidc_wellknown.tmpl View File

@@ -18,7 +18,8 @@
"scopes_supported": [
"openid",
"profile",
"email"
"email",
"groups"
],
"claims_supported": [
"aud",
@@ -34,7 +35,8 @@
"locale",
"updated_at",
"email",
"email_verified"
"email_verified",
"groups"
],
"code_challenge_methods_supported": [
"plain",

Loading…
Cancel
Save