forked from NYANDEV/forgejo
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:
parent
af96286f22
commit
870f5fbc41
3 changed files with 57 additions and 7 deletions
|
@ -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 {
|
||||||
|
@ -227,11 +238,12 @@ func newAccessTokenResponse(grant *login.OAuth2Grant, serverKey, clientKey oauth
|
||||||
}
|
}
|
||||||
|
|
||||||
type userInfoResponse struct {
|
type userInfoResponse struct {
|
||||||
Sub string `json:"sub"`
|
Sub string `json:"sub"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue