]> source.dussan.org Git - gitea.git/commitdiff
Add groups scope/claim to OIDC/OAuth2 Provider (#17367)
authorNico Schieder <Nico.schieder@gmail.com>
Fri, 22 Oct 2021 09:19:24 +0000 (11:19 +0200)
committerGitHub <noreply@github.com>
Fri, 22 Oct 2021 09:19:24 +0000 (17:19 +0800)
* 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
services/auth/source/oauth2/token.go
templates/user/auth/oidc_wellknown.tmpl

index d9fc5eeaf9232443969af52a0d105ad4a5496d77..642a7f33b00a84f53b23ccde174f06417cf767a9 100644 (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 {
index 16d1220842d3ab49640cb0bd4c22232a1cb9bdeb..0c7c5d8caad7b73fb1b9b298496d772a1ea951d5 100644 (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
index d4cbf7dfec4409f1474e313b0def5033c72fe1a3..38e6900c387a34cf0358a826d9893357f2d10fbe 100644 (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",