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>
This commit is contained in:
Nico Schieder 2021-10-22 11:19:24 +02:00 committed by GitHub
parent af96286f22
commit 870f5fbc41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 7 deletions

View file

@ -207,6 +207,17 @@ func newAccessTokenResponse(grant *login.OAuth2Grant, serverKey, clientKey oauth
idToken.Email = user.Email idToken.Email = user.Email
idToken.EmailVerified = user.IsActive 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) signedIDToken, err = idToken.SignToken(clientKey)
if err != nil { if err != nil {
@ -232,6 +243,7 @@ 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"`
} }
// InfoOAuth manages request for userinfo endpoint // InfoOAuth manages request for userinfo endpoint
@ -241,6 +253,7 @@ func InfoOAuth(ctx *context.Context) {
ctx.HandleText(http.StatusUnauthorized, "no valid authorization") ctx.HandleText(http.StatusUnauthorized, "no valid authorization")
return return
} }
response := &userInfoResponse{ response := &userInfoResponse{
Sub: fmt.Sprint(ctx.User.ID), Sub: fmt.Sprint(ctx.User.ID),
Name: ctx.User.FullName, Name: ctx.User.FullName,
@ -248,9 +261,41 @@ func InfoOAuth(ctx *context.Context) {
Email: ctx.User.Email, Email: ctx.User.Email,
Picture: ctx.User.AvatarLink(), 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) 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 // IntrospectOAuth introspects an oauth token
func IntrospectOAuth(ctx *context.Context) { func IntrospectOAuth(ctx *context.Context) {
if ctx.User == nil { if ctx.User == nil {

View file

@ -83,6 +83,9 @@ type OIDCToken struct {
// Scope email // Scope email
Email string `json:"email,omitempty"` Email string `json:"email,omitempty"`
EmailVerified bool `json:"email_verified,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 // SignToken signs an id_token with the (symmetric) client secret key

View file

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