From 870f5fbc4146110be37c28e670e57568d3db3288 Mon Sep 17 00:00:00 2001
From: Nico Schieder <Nico.schieder@gmail.com>
Date: Fri, 22 Oct 2021 11:19:24 +0200
Subject: [PATCH] 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>
---
 routers/web/user/oauth.go               | 55 ++++++++++++++++++++++---
 services/auth/source/oauth2/token.go    |  3 ++
 templates/user/auth/oidc_wellknown.tmpl |  6 ++-
 3 files changed, 57 insertions(+), 7 deletions(-)

diff --git a/routers/web/user/oauth.go b/routers/web/user/oauth.go
index d9fc5eeaf9..642a7f33b0 100644
--- a/routers/web/user/oauth.go
+++ b/routers/web/user/oauth.go
@@ -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 {
diff --git a/services/auth/source/oauth2/token.go b/services/auth/source/oauth2/token.go
index 16d1220842..0c7c5d8caa 100644
--- a/services/auth/source/oauth2/token.go
+++ b/services/auth/source/oauth2/token.go
@@ -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
diff --git a/templates/user/auth/oidc_wellknown.tmpl b/templates/user/auth/oidc_wellknown.tmpl
index d4cbf7dfec..38e6900c38 100644
--- a/templates/user/auth/oidc_wellknown.tmpl
+++ b/templates/user/auth/oidc_wellknown.tmpl
@@ -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",