mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Add Option to synchronize Admin & Restricted states from OIDC/OAuth2 along with Setting Scopes (#16766)
* Add setting to OAuth handlers to override local 2FA settings This PR adds a setting to OAuth and OpenID login sources to allow the source to override local 2FA requirements. Fix #13939 Signed-off-by: Andrew Thornton <art27@cantab.net> * Fix regression from #16544 Signed-off-by: Andrew Thornton <art27@cantab.net> * Add scopes settings Signed-off-by: Andrew Thornton <art27@cantab.net> * fix trace logging in auth_openid Signed-off-by: Andrew Thornton <art27@cantab.net> * add required claim options Signed-off-by: Andrew Thornton <art27@cantab.net> * Move UpdateExternalUser to externalaccount Signed-off-by: Andrew Thornton <art27@cantab.net> * Allow OAuth2/OIDC to set Admin/Restricted status Signed-off-by: Andrew Thornton <art27@cantab.net> * Allow use of the same group claim name for the prohibit login value Signed-off-by: Andrew Thornton <art27@cantab.net> * fixup! Move UpdateExternalUser to externalaccount * as per wxiaoguang Signed-off-by: Andrew Thornton <art27@cantab.net> * add label back in Signed-off-by: Andrew Thornton <art27@cantab.net> * adjust localisation Signed-off-by: Andrew Thornton <art27@cantab.net> * placate lint Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
		
							
								
								
									
										58
									
								
								cmd/admin.go
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								cmd/admin.go
									
									
									
									
									
								
							@@ -299,6 +299,36 @@ var (
 | 
				
			|||||||
			Name:  "skip-local-2fa",
 | 
								Name:  "skip-local-2fa",
 | 
				
			||||||
			Usage: "Set to true to skip local 2fa for users authenticated by this source",
 | 
								Usage: "Set to true to skip local 2fa for users authenticated by this source",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							cli.StringSliceFlag{
 | 
				
			||||||
 | 
								Name:  "scopes",
 | 
				
			||||||
 | 
								Value: nil,
 | 
				
			||||||
 | 
								Usage: "Scopes to request when to authenticate against this OAuth2 source",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							cli.StringFlag{
 | 
				
			||||||
 | 
								Name:  "required-claim-name",
 | 
				
			||||||
 | 
								Value: "",
 | 
				
			||||||
 | 
								Usage: "Claim name that has to be set to allow users to login with this source",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							cli.StringFlag{
 | 
				
			||||||
 | 
								Name:  "required-claim-value",
 | 
				
			||||||
 | 
								Value: "",
 | 
				
			||||||
 | 
								Usage: "Claim value that has to be set to allow users to login with this source",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							cli.StringFlag{
 | 
				
			||||||
 | 
								Name:  "group-claim-name",
 | 
				
			||||||
 | 
								Value: "",
 | 
				
			||||||
 | 
								Usage: "Claim name providing group names for this source",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							cli.StringFlag{
 | 
				
			||||||
 | 
								Name:  "admin-group",
 | 
				
			||||||
 | 
								Value: "",
 | 
				
			||||||
 | 
								Usage: "Group Claim value for administrator users",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							cli.StringFlag{
 | 
				
			||||||
 | 
								Name:  "restricted-group",
 | 
				
			||||||
 | 
								Value: "",
 | 
				
			||||||
 | 
								Usage: "Group Claim value for restricted users",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	microcmdAuthUpdateOauth = cli.Command{
 | 
						microcmdAuthUpdateOauth = cli.Command{
 | 
				
			||||||
@@ -649,6 +679,12 @@ func parseOAuth2Config(c *cli.Context) *oauth2.Source {
 | 
				
			|||||||
		CustomURLMapping:              customURLMapping,
 | 
							CustomURLMapping:              customURLMapping,
 | 
				
			||||||
		IconURL:                       c.String("icon-url"),
 | 
							IconURL:                       c.String("icon-url"),
 | 
				
			||||||
		SkipLocalTwoFA:                c.Bool("skip-local-2fa"),
 | 
							SkipLocalTwoFA:                c.Bool("skip-local-2fa"),
 | 
				
			||||||
 | 
							Scopes:                        c.StringSlice("scopes"),
 | 
				
			||||||
 | 
							RequiredClaimName:             c.String("required-claim-name"),
 | 
				
			||||||
 | 
							RequiredClaimValue:            c.String("required-claim-value"),
 | 
				
			||||||
 | 
							GroupClaimName:                c.String("group-claim-name"),
 | 
				
			||||||
 | 
							AdminGroup:                    c.String("admin-group"),
 | 
				
			||||||
 | 
							RestrictedGroup:               c.String("restricted-group"),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -711,6 +747,28 @@ func runUpdateOauth(c *cli.Context) error {
 | 
				
			|||||||
		oAuth2Config.IconURL = c.String("icon-url")
 | 
							oAuth2Config.IconURL = c.String("icon-url")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.IsSet("scopes") {
 | 
				
			||||||
 | 
							oAuth2Config.Scopes = c.StringSlice("scopes")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.IsSet("required-claim-name") {
 | 
				
			||||||
 | 
							oAuth2Config.RequiredClaimName = c.String("required-claim-name")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if c.IsSet("required-claim-value") {
 | 
				
			||||||
 | 
							oAuth2Config.RequiredClaimValue = c.String("required-claim-value")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.IsSet("group-claim-name") {
 | 
				
			||||||
 | 
							oAuth2Config.GroupClaimName = c.String("group-claim-name")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if c.IsSet("admin-group") {
 | 
				
			||||||
 | 
							oAuth2Config.AdminGroup = c.String("admin-group")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if c.IsSet("restricted-group") {
 | 
				
			||||||
 | 
							oAuth2Config.RestrictedGroup = c.String("restricted-group")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// update custom URL mapping
 | 
						// update custom URL mapping
 | 
				
			||||||
	var customURLMapping = &oauth2.CustomURLMapping{}
 | 
						var customURLMapping = &oauth2.CustomURLMapping{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -129,6 +129,13 @@ Admin operations:
 | 
				
			|||||||
        - `--custom-profile-url`: Use a custom Profile URL (option for GitLab/GitHub).
 | 
					        - `--custom-profile-url`: Use a custom Profile URL (option for GitLab/GitHub).
 | 
				
			||||||
        - `--custom-email-url`: Use a custom Email URL (option for GitHub).
 | 
					        - `--custom-email-url`: Use a custom Email URL (option for GitHub).
 | 
				
			||||||
        - `--icon-url`: Custom icon URL for OAuth2 login source.
 | 
					        - `--icon-url`: Custom icon URL for OAuth2 login source.
 | 
				
			||||||
 | 
					        - `--override-local-2fa`: Allow source to override local 2fa. (Optional)
 | 
				
			||||||
 | 
					        - `--scopes`: Addtional scopes to request for this OAuth2 source. (Optional)
 | 
				
			||||||
 | 
					        - `--required-claim-name`: Claim name that has to be set to allow users to login with this source. (Optional)
 | 
				
			||||||
 | 
					        - `--required-claim-value`: Claim value that has to be set to allow users to login with this source. (Optional)
 | 
				
			||||||
 | 
					        - `--group-claim-name`: Claim name providing group names for this source. (Optional)
 | 
				
			||||||
 | 
					        - `--admin-group`: Group Claim value for administrator users. (Optional)
 | 
				
			||||||
 | 
					        - `--restricted-group`: Group Claim value for restricted users. (Optional)
 | 
				
			||||||
      - Examples:
 | 
					      - Examples:
 | 
				
			||||||
        - `gitea admin auth add-oauth --name external-github --provider github --key OBTAIN_FROM_SOURCE --secret OBTAIN_FROM_SOURCE`
 | 
					        - `gitea admin auth add-oauth --name external-github --provider github --key OBTAIN_FROM_SOURCE --secret OBTAIN_FROM_SOURCE`
 | 
				
			||||||
    - `update-oauth`:
 | 
					    - `update-oauth`:
 | 
				
			||||||
@@ -145,6 +152,13 @@ Admin operations:
 | 
				
			|||||||
        - `--custom-profile-url`: Use a custom Profile URL (option for GitLab/GitHub).
 | 
					        - `--custom-profile-url`: Use a custom Profile URL (option for GitLab/GitHub).
 | 
				
			||||||
        - `--custom-email-url`: Use a custom Email URL (option for GitHub).
 | 
					        - `--custom-email-url`: Use a custom Email URL (option for GitHub).
 | 
				
			||||||
        - `--icon-url`: Custom icon URL for OAuth2 login source.
 | 
					        - `--icon-url`: Custom icon URL for OAuth2 login source.
 | 
				
			||||||
 | 
					        - `--override-local-2fa`: Allow source to override local 2fa. (Optional)
 | 
				
			||||||
 | 
					        - `--scopes`: Addtional scopes to request for this OAuth2 source.
 | 
				
			||||||
 | 
					        - `--required-claim-name`: Claim name that has to be set to allow users to login with this source. (Optional)
 | 
				
			||||||
 | 
					        - `--required-claim-value`: Claim value that has to be set to allow users to login with this source. (Optional)
 | 
				
			||||||
 | 
					        - `--group-claim-name`: Claim name providing group names for this source. (Optional)
 | 
				
			||||||
 | 
					        - `--admin-group`: Group Claim value for administrator users. (Optional)
 | 
				
			||||||
 | 
					        - `--restricted-group`: Group Claim value for restricted users. (Optional)
 | 
				
			||||||
      - Examples:
 | 
					      - Examples:
 | 
				
			||||||
        - `gitea admin auth update-oauth --id 1 --name external-github-updated`
 | 
					        - `gitea admin auth update-oauth --id 1 --name external-github-updated`
 | 
				
			||||||
    - `add-ldap`: Add new LDAP (via Bind DN) authentication source
 | 
					    - `add-ldap`: Add new LDAP (via Bind DN) authentication source
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,9 +10,7 @@ import (
 | 
				
			|||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	"code.gitea.io/gitea/models/login"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/markbates/goth"
 | 
					 | 
				
			||||||
	"xorm.io/builder"
 | 
						"xorm.io/builder"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -139,42 +137,18 @@ func GetUserIDByExternalUserID(provider, userID string) (int64, error) {
 | 
				
			|||||||
	return id, nil
 | 
						return id, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateExternalUser updates external user's information
 | 
					// UpdateExternalUserByExternalID updates an external user's information
 | 
				
			||||||
func UpdateExternalUser(user *User, gothUser goth.User) error {
 | 
					func UpdateExternalUserByExternalID(external *ExternalLoginUser) error {
 | 
				
			||||||
	loginSource, err := login.GetActiveOAuth2LoginSourceByName(gothUser.Provider)
 | 
						has, err := db.GetEngine(db.DefaultContext).Where("external_id=? AND login_source_id=?", external.ExternalID, external.LoginSourceID).
 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	externalLoginUser := &ExternalLoginUser{
 | 
					 | 
				
			||||||
		ExternalID:        gothUser.UserID,
 | 
					 | 
				
			||||||
		UserID:            user.ID,
 | 
					 | 
				
			||||||
		LoginSourceID:     loginSource.ID,
 | 
					 | 
				
			||||||
		RawData:           gothUser.RawData,
 | 
					 | 
				
			||||||
		Provider:          gothUser.Provider,
 | 
					 | 
				
			||||||
		Email:             gothUser.Email,
 | 
					 | 
				
			||||||
		Name:              gothUser.Name,
 | 
					 | 
				
			||||||
		FirstName:         gothUser.FirstName,
 | 
					 | 
				
			||||||
		LastName:          gothUser.LastName,
 | 
					 | 
				
			||||||
		NickName:          gothUser.NickName,
 | 
					 | 
				
			||||||
		Description:       gothUser.Description,
 | 
					 | 
				
			||||||
		AvatarURL:         gothUser.AvatarURL,
 | 
					 | 
				
			||||||
		Location:          gothUser.Location,
 | 
					 | 
				
			||||||
		AccessToken:       gothUser.AccessToken,
 | 
					 | 
				
			||||||
		AccessTokenSecret: gothUser.AccessTokenSecret,
 | 
					 | 
				
			||||||
		RefreshToken:      gothUser.RefreshToken,
 | 
					 | 
				
			||||||
		ExpiresAt:         gothUser.ExpiresAt,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	has, err := db.GetEngine(db.DefaultContext).Where("external_id=? AND login_source_id=?", gothUser.UserID, loginSource.ID).
 | 
					 | 
				
			||||||
		NoAutoCondition().
 | 
							NoAutoCondition().
 | 
				
			||||||
		Exist(externalLoginUser)
 | 
							Exist(external)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	} else if !has {
 | 
						} else if !has {
 | 
				
			||||||
		return ErrExternalLoginUserNotExist{user.ID, loginSource.ID}
 | 
							return ErrExternalLoginUserNotExist{external.UserID, external.LoginSourceID}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = db.GetEngine(db.DefaultContext).Where("external_id=? AND login_source_id=?", gothUser.UserID, loginSource.ID).AllCols().Update(externalLoginUser)
 | 
						_, err = db.GetEngine(db.DefaultContext).Where("external_id=? AND login_source_id=?", external.ExternalID, external.LoginSourceID).AllCols().Update(external)
 | 
				
			||||||
	return err
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -377,6 +377,7 @@ func NewFuncMap() []template.FuncMap {
 | 
				
			|||||||
		"MermaidMaxSourceCharacters": func() int {
 | 
							"MermaidMaxSourceCharacters": func() int {
 | 
				
			||||||
			return setting.MermaidMaxSourceCharacters
 | 
								return setting.MermaidMaxSourceCharacters
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							"Join":        strings.Join,
 | 
				
			||||||
		"QueryEscape": url.QueryEscape,
 | 
							"QueryEscape": url.QueryEscape,
 | 
				
			||||||
	}}
 | 
						}}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2521,6 +2521,11 @@ auths.oauth2_emailURL = Email URL
 | 
				
			|||||||
auths.skip_local_two_fa = Skip local 2FA
 | 
					auths.skip_local_two_fa = Skip local 2FA
 | 
				
			||||||
auths.skip_local_two_fa_helper = Leaving unset means local users with 2FA set will still have to pass 2FA to log on
 | 
					auths.skip_local_two_fa_helper = Leaving unset means local users with 2FA set will still have to pass 2FA to log on
 | 
				
			||||||
auths.oauth2_tenant = Tenant
 | 
					auths.oauth2_tenant = Tenant
 | 
				
			||||||
 | 
					auths.oauth2_scopes = Additional Scopes
 | 
				
			||||||
 | 
					auths.oauth2_required_claim_name = Required Claim Name
 | 
				
			||||||
 | 
					auths.oauth2_required_claim_name_helper = Set this name to restrict login from this source to users with a claim with this name
 | 
				
			||||||
 | 
					auths.oauth2_required_claim_value = Required Claim Value
 | 
				
			||||||
 | 
					auths.oauth2_required_claim_value_helper = Set this value to restrict login from this source to users with a claim with this name and value
 | 
				
			||||||
auths.enable_auto_register = Enable Auto Registration
 | 
					auths.enable_auto_register = Enable Auto Registration
 | 
				
			||||||
auths.sspi_auto_create_users = Automatically create users
 | 
					auths.sspi_auto_create_users = Automatically create users
 | 
				
			||||||
auths.sspi_auto_create_users_helper = Allow SSPI auth method to automatically create new accounts for users that login for the first time
 | 
					auths.sspi_auto_create_users_helper = Allow SSPI auth method to automatically create new accounts for users that login for the first time
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@ import (
 | 
				
			|||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models/login"
 | 
						"code.gitea.io/gitea/models/login"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/auth/pam"
 | 
						"code.gitea.io/gitea/modules/auth/pam"
 | 
				
			||||||
@@ -187,6 +188,9 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source {
 | 
				
			|||||||
		OpenIDConnectAutoDiscoveryURL: form.OpenIDConnectAutoDiscoveryURL,
 | 
							OpenIDConnectAutoDiscoveryURL: form.OpenIDConnectAutoDiscoveryURL,
 | 
				
			||||||
		CustomURLMapping:              customURLMapping,
 | 
							CustomURLMapping:              customURLMapping,
 | 
				
			||||||
		IconURL:                       form.Oauth2IconURL,
 | 
							IconURL:                       form.Oauth2IconURL,
 | 
				
			||||||
 | 
							Scopes:                        strings.Split(form.Oauth2Scopes, ","),
 | 
				
			||||||
 | 
							RequiredClaimName:             form.Oauth2RequiredClaimName,
 | 
				
			||||||
 | 
							RequiredClaimValue:            form.Oauth2RequiredClaimValue,
 | 
				
			||||||
		SkipLocalTwoFA:                form.SkipLocalTwoFA,
 | 
							SkipLocalTwoFA:                form.SkipLocalTwoFA,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -329,8 +333,8 @@ func EditAuthSource(ctx *context.Context) {
 | 
				
			|||||||
				break
 | 
									break
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.HTML(http.StatusOK, tplAuthEdit)
 | 
						ctx.HTML(http.StatusOK, tplAuthEdit)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -320,16 +320,8 @@ func TwoFactorPost(ctx *context.Context) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if ctx.Session.Get("linkAccount") != nil {
 | 
							if ctx.Session.Get("linkAccount") != nil {
 | 
				
			||||||
			gothUser := ctx.Session.Get("linkAccountGothUser")
 | 
								if err := externalaccount.LinkAccountFromStore(ctx.Session, u); err != nil {
 | 
				
			||||||
			if gothUser == nil {
 | 
					 | 
				
			||||||
				ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
 | 
					 | 
				
			||||||
				return
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			err = externalaccount.LinkAccountToUser(u, gothUser.(goth.User))
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				ctx.ServerError("UserSignIn", err)
 | 
									ctx.ServerError("UserSignIn", err)
 | 
				
			||||||
				return
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -506,16 +498,8 @@ func U2FSign(ctx *context.Context) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if ctx.Session.Get("linkAccount") != nil {
 | 
								if ctx.Session.Get("linkAccount") != nil {
 | 
				
			||||||
				gothUser := ctx.Session.Get("linkAccountGothUser")
 | 
									if err := externalaccount.LinkAccountFromStore(ctx.Session, user); err != nil {
 | 
				
			||||||
				if gothUser == nil {
 | 
					 | 
				
			||||||
					ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
 | 
					 | 
				
			||||||
					return
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				err = externalaccount.LinkAccountToUser(user, gothUser.(goth.User))
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					ctx.ServerError("UserSignIn", err)
 | 
										ctx.ServerError("UserSignIn", err)
 | 
				
			||||||
					return
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			redirect := handleSignInFull(ctx, user, remember, false)
 | 
								redirect := handleSignInFull(ctx, user, remember, false)
 | 
				
			||||||
@@ -653,6 +637,13 @@ func SignInOAuthCallback(ctx *context.Context) {
 | 
				
			|||||||
	u, gothUser, err := oAuth2UserLoginCallback(loginSource, ctx.Req, ctx.Resp)
 | 
						u, gothUser, err := oAuth2UserLoginCallback(loginSource, ctx.Req, ctx.Resp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if user_model.IsErrUserProhibitLogin(err) {
 | 
				
			||||||
 | 
								uplerr := err.(*user_model.ErrUserProhibitLogin)
 | 
				
			||||||
 | 
								log.Info("Failed authentication attempt for %s from %s: %v", uplerr.Name, ctx.RemoteAddr(), err)
 | 
				
			||||||
 | 
								ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
 | 
				
			||||||
 | 
								ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		ctx.ServerError("UserSignIn", err)
 | 
							ctx.ServerError("UserSignIn", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -690,6 +681,8 @@ func SignInOAuthCallback(ctx *context.Context) {
 | 
				
			|||||||
				IsRestricted: setting.Service.DefaultUserIsRestricted,
 | 
									IsRestricted: setting.Service.DefaultUserIsRestricted,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								setUserGroupClaims(loginSource, u, &gothUser)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
 | 
								if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
 | 
				
			||||||
				// error already handled
 | 
									// error already handled
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
@@ -704,6 +697,53 @@ func SignInOAuthCallback(ctx *context.Context) {
 | 
				
			|||||||
	handleOAuth2SignIn(ctx, loginSource, u, gothUser)
 | 
						handleOAuth2SignIn(ctx, loginSource, u, gothUser)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func claimValueToStringSlice(claimValue interface{}) []string {
 | 
				
			||||||
 | 
						var groups []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch rawGroup := claimValue.(type) {
 | 
				
			||||||
 | 
						case []string:
 | 
				
			||||||
 | 
							groups = rawGroup
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							str := fmt.Sprintf("%s", rawGroup)
 | 
				
			||||||
 | 
							groups = strings.Split(str, ",")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return groups
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func setUserGroupClaims(loginSource *login.Source, u *user_model.User, gothUser *goth.User) bool {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						source := loginSource.Cfg.(*oauth2.Source)
 | 
				
			||||||
 | 
						if source.GroupClaimName == "" || (source.AdminGroup == "" && source.RestrictedGroup == "") {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						groupClaims, has := gothUser.RawData[source.GroupClaimName]
 | 
				
			||||||
 | 
						if !has {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						groups := claimValueToStringSlice(groupClaims)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wasAdmin, wasRestricted := u.IsAdmin, u.IsRestricted
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if source.AdminGroup != "" {
 | 
				
			||||||
 | 
							u.IsAdmin = false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if source.RestrictedGroup != "" {
 | 
				
			||||||
 | 
							u.IsRestricted = false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, g := range groups {
 | 
				
			||||||
 | 
							if source.AdminGroup != "" && g == source.AdminGroup {
 | 
				
			||||||
 | 
								u.IsAdmin = true
 | 
				
			||||||
 | 
							} else if source.RestrictedGroup != "" && g == source.RestrictedGroup {
 | 
				
			||||||
 | 
								u.IsRestricted = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return wasAdmin != u.IsAdmin || wasRestricted != u.IsRestricted
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getUserName(gothUser *goth.User) string {
 | 
					func getUserName(gothUser *goth.User) string {
 | 
				
			||||||
	switch setting.OAuth2Client.Username {
 | 
						switch setting.OAuth2Client.Username {
 | 
				
			||||||
	case setting.OAuth2UsernameEmail:
 | 
						case setting.OAuth2UsernameEmail:
 | 
				
			||||||
@@ -774,13 +814,21 @@ func handleOAuth2SignIn(ctx *context.Context, source *login.Source, u *user_mode
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Register last login
 | 
							// Register last login
 | 
				
			||||||
		u.SetLastLogin()
 | 
							u.SetLastLogin()
 | 
				
			||||||
		if err := user_model.UpdateUserCols(db.DefaultContext, u, "last_login_unix"); err != nil {
 | 
					
 | 
				
			||||||
 | 
							// Update GroupClaims
 | 
				
			||||||
 | 
							changed := setUserGroupClaims(source, u, &gothUser)
 | 
				
			||||||
 | 
							cols := []string{"last_login_unix"}
 | 
				
			||||||
 | 
							if changed {
 | 
				
			||||||
 | 
								cols = append(cols, "is_admin", "is_restricted")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := user_model.UpdateUserCols(db.DefaultContext, u, cols...); err != nil {
 | 
				
			||||||
			ctx.ServerError("UpdateUserCols", err)
 | 
								ctx.ServerError("UpdateUserCols", err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// update external user information
 | 
							// update external user information
 | 
				
			||||||
		if err := user_model.UpdateExternalUser(u, gothUser); err != nil {
 | 
							if err := externalaccount.UpdateExternalUser(u, gothUser); err != nil {
 | 
				
			||||||
			log.Error("UpdateExternalUser failed: %v", err)
 | 
								log.Error("UpdateExternalUser failed: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -794,6 +842,14 @@ func handleOAuth2SignIn(ctx *context.Context, source *login.Source, u *user_mode
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						changed := setUserGroupClaims(source, u, &gothUser)
 | 
				
			||||||
 | 
						if changed {
 | 
				
			||||||
 | 
							if err := user_model.UpdateUserCols(db.DefaultContext, u, "is_admin", "is_restricted"); err != nil {
 | 
				
			||||||
 | 
								ctx.ServerError("UpdateUserCols", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// User needs to use 2FA, save data and redirect to 2FA page.
 | 
						// User needs to use 2FA, save data and redirect to 2FA page.
 | 
				
			||||||
	if err := ctx.Session.Set("twofaUid", u.ID); err != nil {
 | 
						if err := ctx.Session.Set("twofaUid", u.ID); err != nil {
 | 
				
			||||||
		log.Error("Error setting twofaUid in session: %v", err)
 | 
							log.Error("Error setting twofaUid in session: %v", err)
 | 
				
			||||||
@@ -818,7 +874,9 @@ func handleOAuth2SignIn(ctx *context.Context, source *login.Source, u *user_mode
 | 
				
			|||||||
// OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful
 | 
					// OAuth2UserLoginCallback attempts to handle the callback from the OAuth2 provider and if successful
 | 
				
			||||||
// login the user
 | 
					// login the user
 | 
				
			||||||
func oAuth2UserLoginCallback(loginSource *login.Source, request *http.Request, response http.ResponseWriter) (*user_model.User, goth.User, error) {
 | 
					func oAuth2UserLoginCallback(loginSource *login.Source, request *http.Request, response http.ResponseWriter) (*user_model.User, goth.User, error) {
 | 
				
			||||||
	gothUser, err := loginSource.Cfg.(*oauth2.Source).Callback(request, response)
 | 
						oauth2Source := loginSource.Cfg.(*oauth2.Source)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gothUser, err := oauth2Source.Callback(request, response)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if err.Error() == "securecookie: the value is too long" || strings.Contains(err.Error(), "Data too long") {
 | 
							if err.Error() == "securecookie: the value is too long" || strings.Contains(err.Error(), "Data too long") {
 | 
				
			||||||
			log.Error("OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider", loginSource.Name, setting.OAuth2.MaxTokenLength)
 | 
								log.Error("OAuth2 Provider %s returned too long a token. Current max: %d. Either increase the [OAuth2] MAX_TOKEN_LENGTH or reduce the information returned from the OAuth2 provider", loginSource.Name, setting.OAuth2.MaxTokenLength)
 | 
				
			||||||
@@ -827,6 +885,27 @@ func oAuth2UserLoginCallback(loginSource *login.Source, request *http.Request, r
 | 
				
			|||||||
		return nil, goth.User{}, err
 | 
							return nil, goth.User{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if oauth2Source.RequiredClaimName != "" {
 | 
				
			||||||
 | 
							claimInterface, has := gothUser.RawData[oauth2Source.RequiredClaimName]
 | 
				
			||||||
 | 
							if !has {
 | 
				
			||||||
 | 
								return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if oauth2Source.RequiredClaimValue != "" {
 | 
				
			||||||
 | 
								groups := claimValueToStringSlice(claimInterface)
 | 
				
			||||||
 | 
								found := false
 | 
				
			||||||
 | 
								for _, group := range groups {
 | 
				
			||||||
 | 
									if group == oauth2Source.RequiredClaimValue {
 | 
				
			||||||
 | 
										found = true
 | 
				
			||||||
 | 
										break
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !found {
 | 
				
			||||||
 | 
									return nil, goth.User{}, user_model.ErrUserProhibitLogin{Name: gothUser.UserID}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	user := &user_model.User{
 | 
						user := &user_model.User{
 | 
				
			||||||
		LoginName:   gothUser.UserID,
 | 
							LoginName:   gothUser.UserID,
 | 
				
			||||||
		LoginType:   login.OAuth2,
 | 
							LoginType:   login.OAuth2,
 | 
				
			||||||
@@ -1354,7 +1433,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// update external user information
 | 
						// update external user information
 | 
				
			||||||
	if gothUser != nil {
 | 
						if gothUser != nil {
 | 
				
			||||||
		if err := user_model.UpdateExternalUser(u, *gothUser); err != nil {
 | 
							if err := externalaccount.UpdateExternalUser(u, *gothUser); err != nil {
 | 
				
			||||||
			log.Error("UpdateExternalUser failed: %v", err)
 | 
								log.Error("UpdateExternalUser failed: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -144,10 +144,10 @@ func SignInOpenIDPost(ctx *context.Context) {
 | 
				
			|||||||
// signInOpenIDVerify handles response from OpenID provider
 | 
					// signInOpenIDVerify handles response from OpenID provider
 | 
				
			||||||
func signInOpenIDVerify(ctx *context.Context) {
 | 
					func signInOpenIDVerify(ctx *context.Context) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Trace("Incoming call to: " + ctx.Req.URL.String())
 | 
						log.Trace("Incoming call to: %s", ctx.Req.URL.String())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fullURL := setting.AppURL + ctx.Req.URL.String()[1:]
 | 
						fullURL := setting.AppURL + ctx.Req.URL.String()[1:]
 | 
				
			||||||
	log.Trace("Full URL: " + fullURL)
 | 
						log.Trace("Full URL: %s", fullURL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var id, err = openid.Verify(fullURL)
 | 
						var id, err = openid.Verify(fullURL)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -157,7 +157,7 @@ func signInOpenIDVerify(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Trace("Verified ID: " + id)
 | 
						log.Trace("Verified ID: %s", id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* Now we should seek for the user and log him in, or prompt
 | 
						/* Now we should seek for the user and log him in, or prompt
 | 
				
			||||||
	 * to register if not found */
 | 
						 * to register if not found */
 | 
				
			||||||
@@ -180,7 +180,7 @@ func signInOpenIDVerify(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Trace("User with openid " + id + " does not exist, should connect or register")
 | 
						log.Trace("User with openid: %s does not exist, should connect or register", id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	parsedURL, err := url.Parse(fullURL)
 | 
						parsedURL, err := url.Parse(fullURL)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -199,7 +199,7 @@ func signInOpenIDVerify(ctx *context.Context) {
 | 
				
			|||||||
	email := values.Get("openid.sreg.email")
 | 
						email := values.Get("openid.sreg.email")
 | 
				
			||||||
	nickname := values.Get("openid.sreg.nickname")
 | 
						nickname := values.Get("openid.sreg.nickname")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Trace("User has email=" + email + " and nickname=" + nickname)
 | 
						log.Trace("User has email=%s and nickname=%s", email, nickname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if email != "" {
 | 
						if email != "" {
 | 
				
			||||||
		u, err = user_model.GetUserByEmail(email)
 | 
							u, err = user_model.GetUserByEmail(email)
 | 
				
			||||||
@@ -213,7 +213,7 @@ func signInOpenIDVerify(ctx *context.Context) {
 | 
				
			|||||||
			log.Error("signInOpenIDVerify: %v", err)
 | 
								log.Error("signInOpenIDVerify: %v", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if u != nil {
 | 
							if u != nil {
 | 
				
			||||||
			log.Trace("Local user " + u.LowerName + " has OpenID provided email " + email)
 | 
								log.Trace("Local user %s has OpenID provided email %s", u.LowerName, email)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -228,7 +228,7 @@ func signInOpenIDVerify(ctx *context.Context) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if u != nil {
 | 
							if u != nil {
 | 
				
			||||||
			log.Trace("Local user " + u.LowerName + " has OpenID provided nickname " + nickname)
 | 
								log.Trace("Local user %s has OpenID provided nickname %s", u.LowerName, nickname)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,7 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CustomProviderNewFn creates a goth.Provider using a custom url mapping
 | 
					// CustomProviderNewFn creates a goth.Provider using a custom url mapping
 | 
				
			||||||
type CustomProviderNewFn func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error)
 | 
					type CustomProviderNewFn func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CustomProvider is a GothProvider that has CustomURL features
 | 
					// CustomProvider is a GothProvider that has CustomURL features
 | 
				
			||||||
type CustomProvider struct {
 | 
					type CustomProvider struct {
 | 
				
			||||||
@@ -35,7 +35,7 @@ func (c *CustomProvider) CustomURLSettings() *CustomURLSettings {
 | 
				
			|||||||
func (c *CustomProvider) CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) {
 | 
					func (c *CustomProvider) CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) {
 | 
				
			||||||
	custom := c.customURLSettings.OverrideWith(source.CustomURLMapping)
 | 
						custom := c.customURLSettings.OverrideWith(source.CustomURLMapping)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return c.newFn(source.ClientID, source.ClientSecret, callbackURL, custom)
 | 
						return c.newFn(source.ClientID, source.ClientSecret, callbackURL, custom, source.Scopes)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewCustomProvider is a constructor function for custom providers
 | 
					// NewCustomProvider is a constructor function for custom providers
 | 
				
			||||||
@@ -60,8 +60,7 @@ func init() {
 | 
				
			|||||||
			ProfileURL: availableAttribute(github.ProfileURL),
 | 
								ProfileURL: availableAttribute(github.ProfileURL),
 | 
				
			||||||
			EmailURL:   availableAttribute(github.EmailURL),
 | 
								EmailURL:   availableAttribute(github.EmailURL),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) {
 | 
							func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
 | 
				
			||||||
			scopes := []string{}
 | 
					 | 
				
			||||||
			if setting.OAuth2Client.EnableAutoRegistration {
 | 
								if setting.OAuth2Client.EnableAutoRegistration {
 | 
				
			||||||
				scopes = append(scopes, "user:email")
 | 
									scopes = append(scopes, "user:email")
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -73,8 +72,9 @@ func init() {
 | 
				
			|||||||
			AuthURL:    availableAttribute(gitlab.AuthURL),
 | 
								AuthURL:    availableAttribute(gitlab.AuthURL),
 | 
				
			||||||
			TokenURL:   availableAttribute(gitlab.TokenURL),
 | 
								TokenURL:   availableAttribute(gitlab.TokenURL),
 | 
				
			||||||
			ProfileURL: availableAttribute(gitlab.ProfileURL),
 | 
								ProfileURL: availableAttribute(gitlab.ProfileURL),
 | 
				
			||||||
		}, func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) {
 | 
							}, func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
 | 
				
			||||||
			return gitlab.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, "read_user"), nil
 | 
								scopes = append(scopes, "read_user")
 | 
				
			||||||
 | 
								return gitlab.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...), nil
 | 
				
			||||||
		}))
 | 
							}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	RegisterGothProvider(NewCustomProvider(
 | 
						RegisterGothProvider(NewCustomProvider(
 | 
				
			||||||
@@ -83,8 +83,8 @@ func init() {
 | 
				
			|||||||
			AuthURL:    requiredAttribute(gitea.AuthURL),
 | 
								AuthURL:    requiredAttribute(gitea.AuthURL),
 | 
				
			||||||
			ProfileURL: requiredAttribute(gitea.ProfileURL),
 | 
								ProfileURL: requiredAttribute(gitea.ProfileURL),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) {
 | 
							func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
 | 
				
			||||||
			return gitea.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL), nil
 | 
								return gitea.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...), nil
 | 
				
			||||||
		}))
 | 
							}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	RegisterGothProvider(NewCustomProvider(
 | 
						RegisterGothProvider(NewCustomProvider(
 | 
				
			||||||
@@ -93,25 +93,31 @@ func init() {
 | 
				
			|||||||
			AuthURL:    requiredAttribute(nextcloud.AuthURL),
 | 
								AuthURL:    requiredAttribute(nextcloud.AuthURL),
 | 
				
			||||||
			ProfileURL: requiredAttribute(nextcloud.ProfileURL),
 | 
								ProfileURL: requiredAttribute(nextcloud.ProfileURL),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) {
 | 
							func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
 | 
				
			||||||
			return nextcloud.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL), nil
 | 
								return nextcloud.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...), nil
 | 
				
			||||||
		}))
 | 
							}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	RegisterGothProvider(NewCustomProvider(
 | 
						RegisterGothProvider(NewCustomProvider(
 | 
				
			||||||
		"mastodon", "Mastodon", &CustomURLSettings{
 | 
							"mastodon", "Mastodon", &CustomURLSettings{
 | 
				
			||||||
			AuthURL: requiredAttribute(mastodon.InstanceURL),
 | 
								AuthURL: requiredAttribute(mastodon.InstanceURL),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) {
 | 
							func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
 | 
				
			||||||
			return mastodon.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL), nil
 | 
								return mastodon.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, scopes...), nil
 | 
				
			||||||
		}))
 | 
							}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	RegisterGothProvider(NewCustomProvider(
 | 
						RegisterGothProvider(NewCustomProvider(
 | 
				
			||||||
		"azureadv2", "Azure AD v2", &CustomURLSettings{
 | 
							"azureadv2", "Azure AD v2", &CustomURLSettings{
 | 
				
			||||||
			Tenant: requiredAttribute("organizations"),
 | 
								Tenant: requiredAttribute("organizations"),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		func(clientID, secret, callbackURL string, custom *CustomURLMapping) (goth.Provider, error) {
 | 
							func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
 | 
				
			||||||
 | 
								azureScopes := make([]azureadv2.ScopeType, len(scopes))
 | 
				
			||||||
 | 
								for i, scope := range scopes {
 | 
				
			||||||
 | 
									azureScopes[i] = azureadv2.ScopeType(scope)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			return azureadv2.New(clientID, secret, callbackURL, azureadv2.ProviderOptions{
 | 
								return azureadv2.New(clientID, secret, callbackURL, azureadv2.ProviderOptions{
 | 
				
			||||||
				Tenant: azureadv2.TenantType(custom.Tenant),
 | 
									Tenant: azureadv2.TenantType(custom.Tenant),
 | 
				
			||||||
 | 
									Scopes: azureScopes,
 | 
				
			||||||
			}), nil
 | 
								}), nil
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	))
 | 
						))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,7 +33,12 @@ func (o *OpenIDProvider) Image() string {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// CreateGothProvider creates a GothProvider from this Provider
 | 
					// CreateGothProvider creates a GothProvider from this Provider
 | 
				
			||||||
func (o *OpenIDProvider) CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) {
 | 
					func (o *OpenIDProvider) CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) {
 | 
				
			||||||
	provider, err := openidConnect.New(source.ClientID, source.ClientSecret, callbackURL, source.OpenIDConnectAutoDiscoveryURL, setting.OAuth2Client.OpenIDConnectScopes...)
 | 
						scopes := setting.OAuth2Client.OpenIDConnectScopes
 | 
				
			||||||
 | 
						if len(scopes) == 0 {
 | 
				
			||||||
 | 
							scopes = append(scopes, source.Scopes...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						provider, err := openidConnect.New(source.ClientID, source.ClientSecret, callbackURL, source.OpenIDConnectAutoDiscoveryURL, scopes...)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, source.OpenIDConnectAutoDiscoveryURL, err)
 | 
							log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, source.OpenIDConnectAutoDiscoveryURL, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,10 @@ type SimpleProvider struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// CreateGothProvider creates a GothProvider from this Provider
 | 
					// CreateGothProvider creates a GothProvider from this Provider
 | 
				
			||||||
func (c *SimpleProvider) CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) {
 | 
					func (c *SimpleProvider) CreateGothProvider(providerName, callbackURL string, source *Source) (goth.Provider, error) {
 | 
				
			||||||
	return c.newFn(source.ClientID, source.ClientSecret, callbackURL, c.scopes...), nil
 | 
						scopes := make([]string, len(c.scopes)+len(source.Scopes))
 | 
				
			||||||
 | 
						copy(scopes, c.scopes)
 | 
				
			||||||
 | 
						copy(scopes[len(c.scopes):], source.Scopes)
 | 
				
			||||||
 | 
						return c.newFn(source.ClientID, source.ClientSecret, callbackURL, scopes...), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewSimpleProvider is a constructor function for simple providers
 | 
					// NewSimpleProvider is a constructor function for simple providers
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,14 @@ type Source struct {
 | 
				
			|||||||
	OpenIDConnectAutoDiscoveryURL string
 | 
						OpenIDConnectAutoDiscoveryURL string
 | 
				
			||||||
	CustomURLMapping              *CustomURLMapping
 | 
						CustomURLMapping              *CustomURLMapping
 | 
				
			||||||
	IconURL                       string
 | 
						IconURL                       string
 | 
				
			||||||
	SkipLocalTwoFA                bool `json:",omitempty"`
 | 
					
 | 
				
			||||||
 | 
						Scopes             []string
 | 
				
			||||||
 | 
						RequiredClaimName  string
 | 
				
			||||||
 | 
						RequiredClaimValue string
 | 
				
			||||||
 | 
						GroupClaimName     string
 | 
				
			||||||
 | 
						AdminGroup         string
 | 
				
			||||||
 | 
						RestrictedGroup    string
 | 
				
			||||||
 | 
						SkipLocalTwoFA     bool `json:",omitempty"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// reference to the loginSource
 | 
						// reference to the loginSource
 | 
				
			||||||
	loginSource *login.Source
 | 
						loginSource *login.Source
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										29
									
								
								services/externalaccount/link.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								services/externalaccount/link.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package externalaccount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
 | 
						"github.com/markbates/goth"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Store represents a thing that stores things
 | 
				
			||||||
 | 
					type Store interface {
 | 
				
			||||||
 | 
						Get(interface{}) interface{}
 | 
				
			||||||
 | 
						Set(interface{}, interface{}) error
 | 
				
			||||||
 | 
						Release() error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LinkAccountFromStore links the provided user with a stored external user
 | 
				
			||||||
 | 
					func LinkAccountFromStore(store Store, user *user_model.User) error {
 | 
				
			||||||
 | 
						gothUser := store.Get("linkAccountGothUser")
 | 
				
			||||||
 | 
						if gothUser == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("not in LinkAccount session")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return LinkAccountToUser(user, gothUser.(goth.User))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -15,14 +15,12 @@ import (
 | 
				
			|||||||
	"github.com/markbates/goth"
 | 
						"github.com/markbates/goth"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LinkAccountToUser link the gothUser to the user
 | 
					func toExternalLoginUser(user *user_model.User, gothUser goth.User) (*user_model.ExternalLoginUser, error) {
 | 
				
			||||||
func LinkAccountToUser(user *user_model.User, gothUser goth.User) error {
 | 
					 | 
				
			||||||
	loginSource, err := login.GetActiveOAuth2LoginSourceByName(gothUser.Provider)
 | 
						loginSource, err := login.GetActiveOAuth2LoginSourceByName(gothUser.Provider)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return &user_model.ExternalLoginUser{
 | 
				
			||||||
	externalLoginUser := &user_model.ExternalLoginUser{
 | 
					 | 
				
			||||||
		ExternalID:        gothUser.UserID,
 | 
							ExternalID:        gothUser.UserID,
 | 
				
			||||||
		UserID:            user.ID,
 | 
							UserID:            user.ID,
 | 
				
			||||||
		LoginSourceID:     loginSource.ID,
 | 
							LoginSourceID:     loginSource.ID,
 | 
				
			||||||
@@ -40,6 +38,14 @@ func LinkAccountToUser(user *user_model.User, gothUser goth.User) error {
 | 
				
			|||||||
		AccessTokenSecret: gothUser.AccessTokenSecret,
 | 
							AccessTokenSecret: gothUser.AccessTokenSecret,
 | 
				
			||||||
		RefreshToken:      gothUser.RefreshToken,
 | 
							RefreshToken:      gothUser.RefreshToken,
 | 
				
			||||||
		ExpiresAt:         gothUser.ExpiresAt,
 | 
							ExpiresAt:         gothUser.ExpiresAt,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LinkAccountToUser link the gothUser to the user
 | 
				
			||||||
 | 
					func LinkAccountToUser(user *user_model.User, gothUser goth.User) error {
 | 
				
			||||||
 | 
						externalLoginUser, err := toExternalLoginUser(user, gothUser)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := user_model.LinkExternalToUser(user, externalLoginUser); err != nil {
 | 
						if err := user_model.LinkExternalToUser(user, externalLoginUser); err != nil {
 | 
				
			||||||
@@ -62,3 +68,13 @@ func LinkAccountToUser(user *user_model.User, gothUser goth.User) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdateExternalUser updates external user's information
 | 
				
			||||||
 | 
					func UpdateExternalUser(user *user_model.User, gothUser goth.User) error {
 | 
				
			||||||
 | 
						externalLoginUser, err := toExternalLoginUser(user, gothUser)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return user_model.UpdateExternalUserByExternalID(externalLoginUser)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,6 +67,12 @@ type AuthenticationForm struct {
 | 
				
			|||||||
	Oauth2EmailURL                string
 | 
						Oauth2EmailURL                string
 | 
				
			||||||
	Oauth2IconURL                 string
 | 
						Oauth2IconURL                 string
 | 
				
			||||||
	Oauth2Tenant                  string
 | 
						Oauth2Tenant                  string
 | 
				
			||||||
 | 
						Oauth2Scopes                  string
 | 
				
			||||||
 | 
						Oauth2RequiredClaimName       string
 | 
				
			||||||
 | 
						Oauth2RequiredClaimValue      string
 | 
				
			||||||
 | 
						Oauth2GroupClaimName          string
 | 
				
			||||||
 | 
						Oauth2AdminGroup              string
 | 
				
			||||||
 | 
						Oauth2RestrictedGroup         string
 | 
				
			||||||
	SkipLocalTwoFA                bool
 | 
						SkipLocalTwoFA                bool
 | 
				
			||||||
	SSPIAutoCreateUsers           bool
 | 
						SSPIAutoCreateUsers           bool
 | 
				
			||||||
	SSPIAutoActivateUsers         bool
 | 
						SSPIAutoActivateUsers         bool
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -286,11 +286,6 @@
 | 
				
			|||||||
							<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}>
 | 
												<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}>
 | 
				
			||||||
							<p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
 | 
												<p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
					</div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					<div class="oauth2_use_custom_url inline field">
 | 
					 | 
				
			||||||
						<div class="ui checkbox">
 | 
					 | 
				
			||||||
							<label><strong>{{.i18n.Tr "admin.auths.oauth2_use_custom_url"}}</strong></label>
 | 
					 | 
				
			||||||
							<input id="oauth2_use_custom_url" name="oauth2_use_custom_url" type="checkbox" {{if $cfg.CustomURLMapping}}checked{{end}}>
 | 
												<input id="oauth2_use_custom_url" name="oauth2_use_custom_url" type="checkbox" {{if $cfg.CustomURLMapping}}checked{{end}}>
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
@@ -323,6 +318,33 @@
 | 
				
			|||||||
						<input id="{{.Name}}_email_url" value="{{.CustomURLSettings.EmailURL.Value}}" data-available="{{.CustomURLSettings.EmailURL.Available}}" data-required="{{.CustomURLSettings.EmailURL.Required}}" type="hidden" />
 | 
											<input id="{{.Name}}_email_url" value="{{.CustomURLSettings.EmailURL.Value}}" data-available="{{.CustomURLSettings.EmailURL.Available}}" data-required="{{.CustomURLSettings.EmailURL.Required}}" type="hidden" />
 | 
				
			||||||
						<input id="{{.Name}}_tenant" value="{{.CustomURLSettings.Tenant.Value}}" data-available="{{.CustomURLSettings.Tenant.Available}}" data-required="{{.CustomURLSettings.Tenant.Required}}" type="hidden" />
 | 
											<input id="{{.Name}}_tenant" value="{{.CustomURLSettings.Tenant.Value}}" data-available="{{.CustomURLSettings.Tenant.Available}}" data-required="{{.CustomURLSettings.Tenant.Required}}" type="hidden" />
 | 
				
			||||||
					{{end}}{{end}}
 | 
										{{end}}{{end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										<div class="field">
 | 
				
			||||||
 | 
											<label for="oauth2_scopes">{{.i18n.Tr "admin.auths.oauth2_scopes"}}</label>
 | 
				
			||||||
 | 
											<input id="oauth2_scopes" name="oauth2_scopes" value="{{if $cfg.Scopes}}{{Join $cfg.Scopes "," }}{{end}}">
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
										<div class="field">
 | 
				
			||||||
 | 
											<label for="oauth2_required_claim_name">{{.i18n.Tr "admin.auths.oauth2_required_claim_name"}}</label>
 | 
				
			||||||
 | 
											<input id="oauth2_required_claim_name" name="oauth2_required_claim_name" values="{{$cfg.RequiredClaimName}}">
 | 
				
			||||||
 | 
											<p class="help">{{.i18n.Tr "admin.auths.oauth2_required_claim_name_helper"}}</p>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
										<div class="field">
 | 
				
			||||||
 | 
											<label for="oauth2_required_claim_value">{{.i18n.Tr "admin.auths.oauth2_required_claim_value"}}</label>
 | 
				
			||||||
 | 
											<input id="oauth2_required_claim_value" name="oauth2_required_claim_value" values="{{$cfg.RequiredClaimValue}}">
 | 
				
			||||||
 | 
											<p class="help">{{.i18n.Tr "admin.auths.oauth2_required_claim_value_helper"}}</p>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
										<div class="field">
 | 
				
			||||||
 | 
											<label for="oauth2_group_claim_name">{{.i18n.Tr "admin.auths.oauth2_group_claim_name"}}</label>
 | 
				
			||||||
 | 
											<input id="oauth2_group_claim_name" name="oauth2_group_claim_name" value="{{$cfg.GroupClaimName}}">
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
										<div class="field">
 | 
				
			||||||
 | 
											<label for="oauth2_admin_group">{{.i18n.Tr "admin.auths.oauth2_admin_group"}}</label>
 | 
				
			||||||
 | 
											<input id="oauth2_admin_group" name="oauth2_admin_group" value="{{$cfg.AdminGroup}}">
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
										<div class="field">
 | 
				
			||||||
 | 
											<label for="oauth2_restricted_group">{{.i18n.Tr "admin.auths.oauth2_restricted_group"}}</label>
 | 
				
			||||||
 | 
											<input id="oauth2_restricted_group" name="oauth2_restricted_group" value="{{$cfg.RestrictedGroup}}">
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
				{{end}}
 | 
									{{end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				<!-- SSPI -->
 | 
									<!-- SSPI -->
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,4 +71,31 @@
 | 
				
			|||||||
		<input id="{{.Name}}_email_url" value="{{.CustomURLSettings.EmailURL.Value}}" data-available="{{.CustomURLSettings.EmailURL.Available}}" data-required="{{.CustomURLSettings.EmailURL.Required}}" type="hidden" />
 | 
							<input id="{{.Name}}_email_url" value="{{.CustomURLSettings.EmailURL.Value}}" data-available="{{.CustomURLSettings.EmailURL.Available}}" data-required="{{.CustomURLSettings.EmailURL.Required}}" type="hidden" />
 | 
				
			||||||
		<input id="{{.Name}}_tenant" value="{{.CustomURLSettings.Tenant.Value}}" data-available="{{.CustomURLSettings.Tenant.Available}}" data-required="{{.CustomURLSettings.Tenant.Required}}" type="hidden" />
 | 
							<input id="{{.Name}}_tenant" value="{{.CustomURLSettings.Tenant.Value}}" data-available="{{.CustomURLSettings.Tenant.Available}}" data-required="{{.CustomURLSettings.Tenant.Required}}" type="hidden" />
 | 
				
			||||||
	{{end}}{{end}}
 | 
						{{end}}{{end}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<div class="field">
 | 
				
			||||||
 | 
							<label for="oauth2_scopes">{{.i18n.Tr "admin.auths.oauth2_scopes"}}</label>
 | 
				
			||||||
 | 
							<input id="oauth2_scopes" name="oauth2_scopes" values="{{.oauth2_scopes}}">
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						<div class="field">
 | 
				
			||||||
 | 
							<label for="oauth2_required_claim_name">{{.i18n.Tr "admin.auths.oauth2_required_claim_name"}}</label>
 | 
				
			||||||
 | 
							<input id="oauth2_required_claim_name" name="oauth2_required_claim_name" values="{{.oauth2_required_claim_name}}">
 | 
				
			||||||
 | 
							<p class="help">{{.i18n.Tr "admin.auths.oauth2_required_claim_name_helper"}}</p>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						<div class="field">
 | 
				
			||||||
 | 
							<label for="oauth2_required_claim_value">{{.i18n.Tr "admin.auths.oauth2_required_claim_value"}}</label>
 | 
				
			||||||
 | 
							<input id="oauth2_required_claim_value" name="oauth2_required_claim_value" values="{{.oauth2_required_claim_value}}">
 | 
				
			||||||
 | 
							<p class="help">{{.i18n.Tr "admin.auths.oauth2_required_claim_value_helper"}}</p>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						<div class="field">
 | 
				
			||||||
 | 
							<label for="oauth2_group_claim_name">{{.i18n.Tr "admin.auths.oauth2_group_claim_name"}}</label>
 | 
				
			||||||
 | 
							<input id="oauth2_group_claim_name" name="oauth2_group_claim_name" value="{{.oauth2_group_claim_name}}">
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						<div class="field">
 | 
				
			||||||
 | 
							<label for="oauth2_admin_group">{{.i18n.Tr "admin.auths.oauth2_admin_group"}}</label>
 | 
				
			||||||
 | 
							<input id="oauth2_admin_group" name="oauth2_admin_group" value="{{.oauth2_group_claim_name}}">
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						<div class="field">
 | 
				
			||||||
 | 
							<label for="oauth2_restricted_group">{{.i18n.Tr "admin.auths.oauth2_restricted_group"}}</label>
 | 
				
			||||||
 | 
							<input id="oauth2_restricted_group" name="oauth2_restricted_group" value="{{.oauth2_group_claim_name}}">
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user