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:
parent
b4782e24d2
commit
0981ec30c3
17 changed files with 344 additions and 88 deletions
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,6 +24,13 @@ type Source struct {
|
||||||
OpenIDConnectAutoDiscoveryURL string
|
OpenIDConnectAutoDiscoveryURL string
|
||||||
CustomURLMapping *CustomURLMapping
|
CustomURLMapping *CustomURLMapping
|
||||||
IconURL string
|
IconURL string
|
||||||
|
|
||||||
|
Scopes []string
|
||||||
|
RequiredClaimName string
|
||||||
|
RequiredClaimValue string
|
||||||
|
GroupClaimName string
|
||||||
|
AdminGroup string
|
||||||
|
RestrictedGroup string
|
||||||
SkipLocalTwoFA bool `json:",omitempty"`
|
SkipLocalTwoFA bool `json:",omitempty"`
|
||||||
|
|
||||||
// reference to the loginSource
|
// reference to the loginSource
|
||||||
|
|
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 a new issue