From a3366c4ffa3825bd0353bcd77e9484ec7fc9056a Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Fri, 19 Oct 2018 11:24:17 +0200 Subject: [PATCH 01/27] Refactored handleOAuth2SignIn in routers/user/auth.go The function handleOAuth2SignIn was called twice but some code path could only be reached by one of the invocations. Moved the unnecessary code path out of handleOAuth2SignIn. Signed-off-by: Martin Michaelis --- routers/user/auth.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/routers/user/auth.go b/routers/user/auth.go index a4a0ee3e6a0ff..191fedd9806f7 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -508,10 +508,10 @@ func SignInOAuth(ctx *context.Context) { } // try to do a direct callback flow, so we don't authenticate the user again but use the valid accesstoken to get the user - user, gothUser, err := oAuth2UserLoginCallback(loginSource, ctx.Req.Request, ctx.Resp) + user, _, err := oAuth2UserLoginCallback(loginSource, ctx.Req.Request, ctx.Resp) if err == nil && user != nil { // we got the user without going through the whole OAuth2 authentication flow again - handleOAuth2SignIn(user, gothUser, ctx, err) + handleOAuth2SignIn(ctx, user) return } @@ -540,10 +540,6 @@ func SignInOAuthCallback(ctx *context.Context) { u, gothUser, err := oAuth2UserLoginCallback(loginSource, ctx.Req.Request, ctx.Resp) - handleOAuth2SignIn(u, gothUser, ctx, err) -} - -func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context, err error) { if err != nil { ctx.ServerError("UserSignIn", err) return @@ -556,9 +552,13 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context return } + handleOAuth2SignIn(ctx, u) +} + +func handleOAuth2SignIn(ctx *context.Context, u *models.User) { // If this user is enrolled in 2FA, we can't sign the user in just yet. // Instead, redirect them to the 2FA authentication page. - _, err = models.GetTwoFactorByUID(u.ID) + _, err := models.GetTwoFactorByUID(u.ID) if err != nil { if models.IsErrTwoFactorNotEnrolled(err) { ctx.Session.Set("uid", u.ID) From 6e2ece448ec9684d4b1e3198c49b82c40a9935ad Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Fri, 19 Oct 2018 12:02:40 +0200 Subject: [PATCH 02/27] Refactored user creation There was common code to create a user and display the correct error message. And after the creation the only user should be an admin and if enabled a confirmation email should be sent. This common code is now abstracted into two functions and a helper function to call both. Signed-off-by: Martin Michaelis --- routers/user/auth.go | 97 ++++++++++++++++++------------------- routers/user/auth_openid.go | 45 ++--------------- 2 files changed, 50 insertions(+), 92 deletions(-) diff --git a/routers/user/auth.go b/routers/user/auth.go index 191fedd9806f7..302676926b980 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -810,49 +810,8 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au LoginName: gothUser.(goth.User).UserID, } - if err := models.CreateUser(u); err != nil { - switch { - case models.IsErrUserAlreadyExist(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplLinkAccount, &form) - case models.IsErrEmailAlreadyUsed(err): - ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplLinkAccount, &form) - case models.IsErrNameReserved(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), tplLinkAccount, &form) - case models.IsErrNamePatternNotAllowed(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplLinkAccount, &form) - default: - ctx.ServerError("CreateUser", err) - } - return - } - log.Trace("Account created: %s", u.Name) - - // Auto-set admin for the only user. - if models.CountUsers() == 1 { - u.IsAdmin = true - u.IsActive = true - u.SetLastLogin() - if err := models.UpdateUserCols(u, "is_admin", "is_active", "last_login_unix"); err != nil { - ctx.ServerError("UpdateUser", err) - return - } - } - - // Send confirmation email - if setting.Service.RegisterEmailConfirm && u.ID > 1 { - models.SendActivateAccountMail(ctx.Context, u) - ctx.Data["IsSendRegisterMail"] = true - ctx.Data["Email"] = u.Email - ctx.Data["ActiveCodeLives"] = base.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language()) - ctx.HTML(200, TplActivate) - - if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { - log.Error(4, "Set cache(MailResendLimit) fail: %v", err) - } + if !createAndHandleCreatedUser(ctx, tplLinkAccount, form, u) { + // error already handled return } @@ -943,27 +902,64 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo Passwd: form.Password, IsActive: !setting.Service.RegisterEmailConfirm, } + + if !createAndHandleCreatedUser(ctx, tplSignUp, form, u) { + // error already handled + return + } + + ctx.Flash.Success(ctx.Tr("auth.sign_up_successful")) + handleSignInFull(ctx, u, false, true) +} + +// CreateAndHandleCreatedUser calls createUserInContext and +// then handleUserCreated. +func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User) (ok bool) { + ok = createUserInContext(ctx, tpl, form, u) + if !ok { + return + } + ok = handleUserCreated(ctx, u) + return +} + +// CreateUserInContext creates a user and handles errors within a given context. +// Optionaly a template can be specified. +func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User) (ok bool) { if err := models.CreateUser(u); err != nil { + // handle error without template + if len(tpl) == 0 { + ctx.ServerError("CreateUser", err) + return + } + + // handle error with template switch { case models.IsErrUserAlreadyExist(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSignUp, &form) + ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tpl, &form) case models.IsErrEmailAlreadyUsed(err): ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSignUp, &form) + ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, &form) case models.IsErrNameReserved(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), tplSignUp, &form) + ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, &form) case models.IsErrNamePatternNotAllowed(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplSignUp, &form) + ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, &form) default: ctx.ServerError("CreateUser", err) } return } log.Trace("Account created: %s", u.Name) + return true +} +// HandleUserCreated does additional steps after a new user is created. +// It auto-sets admin for the only user and +// sends a confirmation email if required. +func handleUserCreated(ctx *context.Context, u *models.User) (ok bool) { // Auto-set admin for the only user. if models.CountUsers() == 1 { u.IsAdmin = true @@ -975,8 +971,8 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo } } - // Send confirmation email, no need for social account. - if setting.Service.RegisterEmailConfirm && u.ID > 1 { + // Send confirmation email + if !u.IsActive && u.ID > 1 { models.SendActivateAccountMail(ctx.Context, u) ctx.Data["IsSendRegisterMail"] = true ctx.Data["Email"] = u.Email @@ -989,8 +985,7 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo return } - ctx.Flash.Success(ctx.Tr("auth.sign_up_successful")) - handleSignInFull(ctx, u, false, true) + return true } // Activate render activate user page diff --git a/routers/user/auth_openid.go b/routers/user/auth_openid.go index 2c5c36a3bc815..ba4bf76f11ede 100644 --- a/routers/user/auth_openid.go +++ b/routers/user/auth_openid.go @@ -366,33 +366,16 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si return } - // TODO: abstract a finalizeSignUp function ? u := &models.User{ Name: form.UserName, Email: form.Email, Passwd: password, IsActive: !setting.Service.RegisterEmailConfirm, } - if err := models.CreateUser(u); err != nil { - switch { - case models.IsErrUserAlreadyExist(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplSignUpOID, &form) - case models.IsErrEmailAlreadyUsed(err): - ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSignUpOID, &form) - case models.IsErrNameReserved(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), tplSignUpOID, &form) - case models.IsErrNamePatternNotAllowed(err): - ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplSignUpOID, &form) - default: - ctx.ServerError("CreateUser", err) - } + if !createUserInContext(ctx, tplSignUpOID, form, u) { + // error already handled return } - log.Trace("Account created: %s", u.Name) // add OpenID for the user userOID := &models.UserOpenID{UID: u.ID, URI: oid} @@ -405,28 +388,8 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si return } - // Auto-set admin for the only user. - if models.CountUsers() == 1 { - u.IsAdmin = true - u.IsActive = true - u.SetLastLogin() - if err := models.UpdateUserCols(u, "is_admin", "is_active", "last_login_unix"); err != nil { - ctx.ServerError("UpdateUser", err) - return - } - } - - // Send confirmation email, no need for social account. - if setting.Service.RegisterEmailConfirm && u.ID > 1 { - models.SendActivateAccountMail(ctx.Context, u) - ctx.Data["IsSendRegisterMail"] = true - ctx.Data["Email"] = u.Email - ctx.Data["ActiveCodeLives"] = base.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language()) - ctx.HTML(200, TplActivate) - - if err := ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil { - log.Error(4, "Set cache(MailResendLimit) fail: %v", err) - } + if !handleUserCreated(ctx, u) { + // error already handled return } From a4738154289e082742bbea194c8d29d225a8f15e Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Fri, 19 Oct 2018 12:13:20 +0200 Subject: [PATCH 03/27] Added auto-register for OAuth2 users If enabled new OAuth2 users will be registered with their OAuth2 details. The UserID, Name and Email fields from the gothUser are used. Therefore the OpenID Connect provider needs additional scopes to return the coresponding claims. Signed-off-by: Martin Michaelis --- custom/conf/app.ini.sample | 5 ++++ .../doc/advanced/config-cheat-sheet.en-us.md | 4 +++ modules/auth/oauth2/oauth2.go | 2 +- modules/setting/setting.go | 16 ++++++++++++ routers/user/auth.go | 26 ++++++++++++++++--- 5 files changed, 48 insertions(+), 5 deletions(-) diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index 47d7bcb6a4050..7b603bbd8bb11 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -310,6 +310,11 @@ REGISTER_EMAIL_CONFIRM = false DISABLE_REGISTRATION = false ; Allow registration only using third part services, it works only when DISABLE_REGISTRATION is false ALLOW_ONLY_EXTERNAL_REGISTRATION = false +; Automatically create user accounts for new oauth2 users. +ENABLE_OAUTH2_AUTO_REGISTRATION = false +; Whether a new auto registered oauth2 user needs to confirm their email. +; Do not include to use the REGISTER_EMAIL_CONFIRM setting. +; OAUTH2_REGISTER_EMAIL_CONFIRM = ; User must sign in to view anything. REQUIRE_SIGNIN_VIEW = false ; Mail notification diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index fef056af8176f..4949d65be615e 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -182,6 +182,10 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. Requires `Mailer` to be enabled. - `DISABLE_REGISTRATION`: **false**: Disable registration, after which only admin can create accounts for users. +- `ENABLE_OAUTH2_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration + for oauth2 authentication. +- `OAUTH2_REGISTER_EMAIL_CONFIRM`: **REGISTER\_EMAIL\_CONFIRM**: Set this to enable or disable + mail confirmation of OAuth2 auto-registration. - `REQUIRE_SIGNIN_VIEW`: **false**: Enable this to force users to log in to view any page. - `ENABLE_NOTIFY_MAIL`: **false**: Enable this to send e-mail to watchers of a repository when something happens, like creating issues. Requires `Mailer` to be enabled. diff --git a/modules/auth/oauth2/oauth2.go b/modules/auth/oauth2/oauth2.go index de125c6195a6e..b2105bdb78765 100644 --- a/modules/auth/oauth2/oauth2.go +++ b/modules/auth/oauth2/oauth2.go @@ -167,7 +167,7 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo case "gplus": provider = gplus.New(clientID, clientSecret, callbackURL, "email") case "openidConnect": - if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL); err != nil { + if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL, setting.Service.OAuth2OpenIDConnectScopes...); err != nil { log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, openIDConnectAutoDiscoveryURL, err) } case "twitter": diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 882f565411a64..65925975849d0 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1203,6 +1203,9 @@ var Service struct { DisableRegistration bool AllowOnlyExternalRegistration bool ShowRegistrationButton bool + EnableOAuth2AutoRegister bool + OAuth2RegisterEmailConfirm bool + OAuth2OpenIDConnectScopes []string RequireSignInView bool EnableNotifyMail bool EnableReverseProxyAuth bool @@ -1233,6 +1236,19 @@ func newService() { Service.DisableRegistration = sec.Key("DISABLE_REGISTRATION").MustBool() Service.AllowOnlyExternalRegistration = sec.Key("ALLOW_ONLY_EXTERNAL_REGISTRATION").MustBool() Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration)) + Service.EnableOAuth2AutoRegister = sec.Key("ENABLE_OAUTH2_AUTO_REGISTRATION").MustBool() + Service.OAuth2RegisterEmailConfirm = sec.Key("OAUTH2_REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) + if !sec.HasKey("OAUTH2_OPENID_CONNECT_SCOPES") && Service.EnableOAuth2AutoRegister { + Service.OAuth2OpenIDConnectScopes = []string{"profile", "email"} + } else { + pats := sec.Key("OAUTH2_OPENID_CONNECT_SCOPES").Strings(" ") + Service.OAuth2OpenIDConnectScopes = make([]string, 0, len(pats)) + for _, scope := range pats { + if scope != "" { + Service.OAuth2OpenIDConnectScopes = append(Service.OAuth2OpenIDConnectScopes, scope) + } + } + } Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool() Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool() Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool() diff --git a/routers/user/auth.go b/routers/user/auth.go index 302676926b980..2b9c6ac73042e 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -546,10 +546,28 @@ func SignInOAuthCallback(ctx *context.Context) { } if u == nil { - // no existing user is found, request attach or new account - ctx.Session.Set("linkAccountGothUser", gothUser) - ctx.Redirect(setting.AppSubURL + "/user/link_account") - return + if setting.Service.EnableOAuth2AutoRegister { + // create new user with details from oauth2 provider + u = &models.User{ + Name: gothUser.UserID, + FullName: gothUser.Name, + Email: gothUser.Email, + IsActive: !setting.Service.OAuth2RegisterEmailConfirm, + LoginType: models.LoginOAuth2, + LoginSource: loginSource.ID, + LoginName: gothUser.UserID, + } + + if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u) { + // error already handled + return + } + } else { + // no existing user is found, request attach or new account + ctx.Session.Set("linkAccountGothUser", gothUser) + ctx.Redirect(setting.AppSubURL + "/user/link_account") + return + } } handleOAuth2SignIn(ctx, u) From a700b02415f5b9ac89ae2a48924042e6a69f9630 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Tue, 23 Oct 2018 02:05:14 +0200 Subject: [PATCH 04/27] Moved oauth2 settings to new section in app.ini Signed-off-by: Martin Michaelis --- custom/conf/app.ini.sample | 16 +++++--- .../doc/advanced/config-cheat-sheet.en-us.md | 13 +++++-- modules/auth/oauth2/oauth2.go | 2 +- modules/setting/setting.go | 39 +++++++++++-------- routers/user/auth.go | 4 +- 5 files changed, 46 insertions(+), 28 deletions(-) diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index 7b603bbd8bb11..f3133e6c0c5da 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -299,6 +299,17 @@ WHITELISTED_URIS = ; Example value: loadaverage.org/badguy stackexchange.com/.*spammer BLACKLISTED_URIS = +[oauth2] +; Whether a new auto registered oauth2 user needs to confirm their email. +; Do not include to use the REGISTER_EMAIL_CONFIRM setting. +;OAUTH2_REGISTER_EMAIL_CONFIRM = +; Scopes for the openid connect oauth2 provider (seperated by space, the openid scope is implicitly added). +; Typical values are profile and email. +; For more information about the possible values see https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims +OAUTH2_OPENID_CONNECT_SCOPES = +; Automatically create user accounts for new oauth2 users. +ENABLE_OAUTH2_AUTO_REGISTRATION = false + [service] ; Time limit to confirm account/email registration ACTIVE_CODE_LIVE_MINUTES = 180 @@ -310,11 +321,6 @@ REGISTER_EMAIL_CONFIRM = false DISABLE_REGISTRATION = false ; Allow registration only using third part services, it works only when DISABLE_REGISTRATION is false ALLOW_ONLY_EXTERNAL_REGISTRATION = false -; Automatically create user accounts for new oauth2 users. -ENABLE_OAUTH2_AUTO_REGISTRATION = false -; Whether a new auto registered oauth2 user needs to confirm their email. -; Do not include to use the REGISTER_EMAIL_CONFIRM setting. -; OAUTH2_REGISTER_EMAIL_CONFIRM = ; User must sign in to view anything. REQUIRE_SIGNIN_VIEW = false ; Mail notification diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 4949d65be615e..1efe1337c71f7 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -173,6 +173,15 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `BLACKLISTED_URIS`: **\**: If non-empty, list of POSIX regex patterns matching OpenID URI's to block. +## OAuth2 (`oauth2`) + +- `OAUTH2_REGISTER_EMAIL_CONFIRM`: **REGISTER\_EMAIL\_CONFIRM**: Set this to enable or disable + mail confirmation of OAuth2 auto-registration. +- `OAUTH2_OPENID_CONNECT_SCOPES`: **\**: List of additional openid connect scopes. + (`openid` is implicitly added) +- `ENABLE_OAUTH2_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration + for oauth2 authentication. + ## Service (`service`) - `ACTIVE_CODE_LIVE_MINUTES`: **180**: Time limit (min) to confirm account/email registration. @@ -182,10 +191,6 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. Requires `Mailer` to be enabled. - `DISABLE_REGISTRATION`: **false**: Disable registration, after which only admin can create accounts for users. -- `ENABLE_OAUTH2_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration - for oauth2 authentication. -- `OAUTH2_REGISTER_EMAIL_CONFIRM`: **REGISTER\_EMAIL\_CONFIRM**: Set this to enable or disable - mail confirmation of OAuth2 auto-registration. - `REQUIRE_SIGNIN_VIEW`: **false**: Enable this to force users to log in to view any page. - `ENABLE_NOTIFY_MAIL`: **false**: Enable this to send e-mail to watchers of a repository when something happens, like creating issues. Requires `Mailer` to be enabled. diff --git a/modules/auth/oauth2/oauth2.go b/modules/auth/oauth2/oauth2.go index b2105bdb78765..32a338d54a96e 100644 --- a/modules/auth/oauth2/oauth2.go +++ b/modules/auth/oauth2/oauth2.go @@ -167,7 +167,7 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo case "gplus": provider = gplus.New(clientID, clientSecret, callbackURL, "email") case "openidConnect": - if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL, setting.Service.OAuth2OpenIDConnectScopes...); err != nil { + if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL, setting.OAuth2.OAuth2OpenIDConnectScopes...); err != nil { log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, openIDConnectAutoDiscoveryURL, err) } case "twitter": diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 65925975849d0..0df9b391bd757 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1203,9 +1203,6 @@ var Service struct { DisableRegistration bool AllowOnlyExternalRegistration bool ShowRegistrationButton bool - EnableOAuth2AutoRegister bool - OAuth2RegisterEmailConfirm bool - OAuth2OpenIDConnectScopes []string RequireSignInView bool EnableNotifyMail bool EnableReverseProxyAuth bool @@ -1236,19 +1233,6 @@ func newService() { Service.DisableRegistration = sec.Key("DISABLE_REGISTRATION").MustBool() Service.AllowOnlyExternalRegistration = sec.Key("ALLOW_ONLY_EXTERNAL_REGISTRATION").MustBool() Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration)) - Service.EnableOAuth2AutoRegister = sec.Key("ENABLE_OAUTH2_AUTO_REGISTRATION").MustBool() - Service.OAuth2RegisterEmailConfirm = sec.Key("OAUTH2_REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) - if !sec.HasKey("OAUTH2_OPENID_CONNECT_SCOPES") && Service.EnableOAuth2AutoRegister { - Service.OAuth2OpenIDConnectScopes = []string{"profile", "email"} - } else { - pats := sec.Key("OAUTH2_OPENID_CONNECT_SCOPES").Strings(" ") - Service.OAuth2OpenIDConnectScopes = make([]string, 0, len(pats)) - for _, scope := range pats { - if scope != "" { - Service.OAuth2OpenIDConnectScopes = append(Service.OAuth2OpenIDConnectScopes, scope) - } - } - } Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool() Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool() Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool() @@ -1285,6 +1269,28 @@ func newService() { } } +// OAuth2 settings +var OAuth2 struct { + OAuth2RegisterEmailConfirm bool + OAuth2OpenIDConnectScopes []string + EnableOAuth2AutoRegister bool +} + +func newOAuth2() { + sec := Cfg.Section("oauth2") + OAuth2.OAuth2RegisterEmailConfirm = sec.Key("OAUTH2_REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) + + pats := sec.Key("OAUTH2_OPENID_CONNECT_SCOPES").Strings(" ") + OAuth2.OAuth2OpenIDConnectScopes = make([]string, 0, len(pats)) + for _, scope := range pats { + if scope != "" { + OAuth2.OAuth2OpenIDConnectScopes = append(OAuth2.OAuth2OpenIDConnectScopes, scope) + } + } + + OAuth2.EnableOAuth2AutoRegister = sec.Key("ENABLE_OAUTH2_AUTO_REGISTRATION").MustBool() +} + var logLevels = map[string]string{ "Trace": "0", "Debug": "1", @@ -1617,6 +1623,7 @@ func newWebhookService() { // NewServices initializes the services func NewServices() { newService() + newOAuth2() newLogService() NewXORMLogService(false) newCacheService() diff --git a/routers/user/auth.go b/routers/user/auth.go index 2b9c6ac73042e..0c8f66913ee73 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -546,13 +546,13 @@ func SignInOAuthCallback(ctx *context.Context) { } if u == nil { - if setting.Service.EnableOAuth2AutoRegister { + if setting.OAuth2.EnableOAuth2AutoRegister { // create new user with details from oauth2 provider u = &models.User{ Name: gothUser.UserID, FullName: gothUser.Name, Email: gothUser.Email, - IsActive: !setting.Service.OAuth2RegisterEmailConfirm, + IsActive: !setting.OAuth2.OAuth2RegisterEmailConfirm, LoginType: models.LoginOAuth2, LoginSource: loginSource.ID, LoginName: gothUser.UserID, From f0124c48de1aab6890151dbcc9282b283173e0f4 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Tue, 23 Oct 2018 02:08:23 +0200 Subject: [PATCH 05/27] Added oauth2 use nickname setting Signed-off-by: Martin Michaelis --- custom/conf/app.ini.sample | 2 ++ docs/content/doc/advanced/config-cheat-sheet.en-us.md | 2 ++ modules/setting/setting.go | 2 ++ routers/user/auth.go | 8 +++++++- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index f3133e6c0c5da..95233d8bd4f04 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -309,6 +309,8 @@ BLACKLISTED_URIS = OAUTH2_OPENID_CONNECT_SCOPES = ; Automatically create user accounts for new oauth2 users. ENABLE_OAUTH2_AUTO_REGISTRATION = false +; Use the nickname attribute from the oauth2 provider instead of the userid as the new username +OAUTH2_USE_NICKNAME = false [service] ; Time limit to confirm account/email registration diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 1efe1337c71f7..084e526255c22 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -181,6 +181,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. (`openid` is implicitly added) - `ENABLE_OAUTH2_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration for oauth2 authentication. +- `OAUTH2_USE_NICKNAME`: **false**: Set this to use the nickname from the oauth2 provider + instead of the userid for the username of the new user. ## Service (`service`) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 0df9b391bd757..0123856b3fb2b 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1274,6 +1274,7 @@ var OAuth2 struct { OAuth2RegisterEmailConfirm bool OAuth2OpenIDConnectScopes []string EnableOAuth2AutoRegister bool + OAuth2UseNickname bool } func newOAuth2() { @@ -1289,6 +1290,7 @@ func newOAuth2() { } OAuth2.EnableOAuth2AutoRegister = sec.Key("ENABLE_OAUTH2_AUTO_REGISTRATION").MustBool() + OAuth2.OAuth2UseNickname = sec.Key("OAUTH2_USE_NICKNAME").MustBool() } var logLevels = map[string]string{ diff --git a/routers/user/auth.go b/routers/user/auth.go index 0c8f66913ee73..2a0191b66e069 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -548,8 +548,14 @@ func SignInOAuthCallback(ctx *context.Context) { if u == nil { if setting.OAuth2.EnableOAuth2AutoRegister { // create new user with details from oauth2 provider + var name string + if setting.OAuth2.OAuth2UseNickname { + name = gothUser.NickName + } else { + name = gothUser.UserID + } u = &models.User{ - Name: gothUser.UserID, + Name: name, FullName: gothUser.Name, Email: gothUser.Email, IsActive: !setting.OAuth2.OAuth2RegisterEmailConfirm, From aeb833c9ad408efdad3cf4fe1fd9e9d997b7f0a8 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Tue, 25 Feb 2020 07:03:57 +0100 Subject: [PATCH 06/27] Moved oauth2_client settings to service file Signed-off-by: Martin Michaelis --- modules/setting/service.go | 20 ++++++++++++++++++++ modules/setting/setting.go | 25 ------------------------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/modules/setting/service.go b/modules/setting/service.go index c463b0a9d5d0d..909dd33011ec0 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -55,6 +55,14 @@ var Service struct { OpenIDBlacklist []*regexp.Regexp } +// OAuth2Client settings +var OAuth2Client struct { + OAuth2RegisterEmailConfirm bool + OAuth2OpenIDConnectScopes []string + EnableOAuth2AutoRegister bool + OAuth2UseNickname bool +} + func newService() { sec := Cfg.Section("service") Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180) @@ -110,4 +118,16 @@ func newService() { Service.OpenIDBlacklist[i] = regexp.MustCompilePOSIX(p) } } + + sec = Cfg.Section("oauth2_client") + OAuth2Client.OAuth2RegisterEmailConfirm = sec.Key("OAUTH2_REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) + pats = sec.Key("OAUTH2_OPENID_CONNECT_SCOPES").Strings(" ") + OAuth2Client.OAuth2OpenIDConnectScopes = make([]string, 0, len(pats)) + for _, scope := range pats { + if scope != "" { + OAuth2Client.OAuth2OpenIDConnectScopes = append(OAuth2Client.OAuth2OpenIDConnectScopes, scope) + } + } + OAuth2Client.EnableOAuth2AutoRegister = sec.Key("ENABLE_OAUTH2_AUTO_REGISTRATION").MustBool() + OAuth2Client.OAuth2UseNickname = sec.Key("OAUTH2_USE_NICKNAME").MustBool() } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index f8c060bc0c5b3..714015c47589d 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1015,30 +1015,6 @@ func NewContext() { } } -// OAuth2Client settings -var OAuth2Client struct { - OAuth2RegisterEmailConfirm bool - OAuth2OpenIDConnectScopes []string - EnableOAuth2AutoRegister bool - OAuth2UseNickname bool -} - -func newOAuth2() { - sec := Cfg.Section("oauth2_client") - OAuth2Client.OAuth2RegisterEmailConfirm = sec.Key("OAUTH2_REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) - - pats := sec.Key("OAUTH2_OPENID_CONNECT_SCOPES").Strings(" ") - OAuth2Client.OAuth2OpenIDConnectScopes = make([]string, 0, len(pats)) - for _, scope := range pats { - if scope != "" { - OAuth2Client.OAuth2OpenIDConnectScopes = append(OAuth2Client.OAuth2OpenIDConnectScopes, scope) - } - } - - OAuth2Client.EnableOAuth2AutoRegister = sec.Key("ENABLE_OAUTH2_AUTO_REGISTRATION").MustBool() - OAuth2Client.OAuth2UseNickname = sec.Key("OAUTH2_USE_NICKNAME").MustBool() -} - func loadInternalToken(sec *ini.Section) string { uri := sec.Key("INTERNAL_TOKEN_URI").String() if len(uri) == 0 { @@ -1113,7 +1089,6 @@ func loadOrGenerateInternalToken(sec *ini.Section) string { func NewServices() { InitDBConfig() newService() - newOAuth2() NewLogServices(false) newCacheService() newSessionService() From 527c7e61d945bb4dad487e7e5efefd736aee4201 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Tue, 25 Feb 2020 07:09:28 +0100 Subject: [PATCH 07/27] Updated comments on auth helpers Signed-off-by: Martin Michaelis --- routers/user/auth.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/routers/user/auth.go b/routers/user/auth.go index ec833d985f083..938c4c797fb17 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -1072,7 +1072,7 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo handleSignInFull(ctx, u, false, true) } -// CreateAndHandleCreatedUser calls createUserInContext and +// createAndHandleCreatedUser calls createUserInContext and // then handleUserCreated. func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User, gothUser *goth.User) (ok bool) { ok = createUserInContext(ctx, tpl, form, u) @@ -1083,8 +1083,8 @@ func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form int return } -// CreateUserInContext creates a user and handles errors within a given context. -// Optionaly a template can be specified. +// createUserInContext creates a user and handles errors within a given context. +// Optionally a template can be specified. func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User) (ok bool) { if err := models.CreateUser(u); err != nil { // handle error without template @@ -1119,8 +1119,8 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{ return true } -// HandleUserCreated does additional steps after a new user is created. -// It auto-sets admin for the only user and +// handleUserCreated does additional steps after a new user is created. +// It auto-sets admin for the only user, updates the optional external user and // sends a confirmation email if required. func handleUserCreated(ctx *context.Context, u *models.User, gothUser *goth.User) (ok bool) { // Auto-set admin for the only user. From 6a3a0e7c0b11e425210720b396720a0d9b3e2349 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Tue, 25 Feb 2020 07:25:14 +0100 Subject: [PATCH 08/27] Renamed oauth2_client settings Signed-off-by: Martin Michaelis --- custom/conf/app.ini.sample | 12 +++++------ .../doc/advanced/config-cheat-sheet.en-us.md | 12 ++++------- modules/auth/oauth2/oauth2.go | 2 +- modules/setting/service.go | 20 +++++++++---------- routers/user/auth.go | 6 +++--- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index 7d11ddedabe51..ebab6022ff456 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -504,16 +504,16 @@ BLACKLISTED_URIS = [oauth2_client] ; Whether a new auto registered oauth2 user needs to confirm their email. -; Do not include to use the REGISTER_EMAIL_CONFIRM setting. -;OAUTH2_REGISTER_EMAIL_CONFIRM = +; Do not include to use the REGISTER_EMAIL_CONFIRM setting from the `[service]` section. +;REGISTER_EMAIL_CONFIRM = ; Scopes for the openid connect oauth2 provider (seperated by space, the openid scope is implicitly added). ; Typical values are profile and email. ; For more information about the possible values see https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims -OAUTH2_OPENID_CONNECT_SCOPES = +OPENID_CONNECT_SCOPES = ; Automatically create user accounts for new oauth2 users. -ENABLE_OAUTH2_AUTO_REGISTRATION = false -; Use the nickname attribute from the oauth2 provider instead of the userid as the new username -OAUTH2_USE_NICKNAME = false +ENABLE_AUTO_REGISTRATION = false +; Use the nickname attribute from the oauth2 provider instead of the userid as the new username. +USE_NICKNAME = false [service] ; Time limit to confirm account/email registration diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 633d9e47428c1..ee16c1417c543 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -310,14 +310,10 @@ set name for unique queues. Individual queues will default to ## OAuth2 Client (`oauth2_client`) -- `OAUTH2_REGISTER_EMAIL_CONFIRM`: **REGISTER\_EMAIL\_CONFIRM**: Set this to enable or disable - mail confirmation of OAuth2 auto-registration. -- `OAUTH2_OPENID_CONNECT_SCOPES`: **\**: List of additional openid connect scopes. - (`openid` is implicitly added) -- `ENABLE_OAUTH2_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration - for oauth2 authentication. -- `OAUTH2_USE_NICKNAME`: **false**: Set this to use the nickname from the oauth2 provider - instead of the userid for the username of the new user. +- `REGISTER_EMAIL_CONFIRM`: *[service]* **REGISTER\_EMAIL\_CONFIRM**: Set this to enable or disable email confirmation of OAuth2 auto-registration. (Overwrites the REGISTER\_EMAIL\_CONFIRM setting of the `[service]` section) +- `OPENID_CONNECT_SCOPES`: **\**: List of additional openid connect scopes. (`openid` is implicitly added) +- `ENABLE_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration for oauth2 authentication. +- `USE_NICKNAME`: **false**: Set this to use the nickname from the oauth2 provider instead of the userid for the username of the new user. ## Service (`service`) diff --git a/modules/auth/oauth2/oauth2.go b/modules/auth/oauth2/oauth2.go index b51d187a4de4a..8191d84db6e1d 100644 --- a/modules/auth/oauth2/oauth2.go +++ b/modules/auth/oauth2/oauth2.go @@ -169,7 +169,7 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo case "gplus": // named gplus due to legacy gplus -> google migration (Google killed Google+). This ensures old connections still work provider = google.New(clientID, clientSecret, callbackURL) case "openidConnect": - if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL, setting.OAuth2Client.OAuth2OpenIDConnectScopes...); err != nil { + if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL, setting.OAuth2Client.OpenIDConnectScopes...); err != nil { log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, openIDConnectAutoDiscoveryURL, err) } case "twitter": diff --git a/modules/setting/service.go b/modules/setting/service.go index 909dd33011ec0..3aeea545976bf 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -57,10 +57,10 @@ var Service struct { // OAuth2Client settings var OAuth2Client struct { - OAuth2RegisterEmailConfirm bool - OAuth2OpenIDConnectScopes []string - EnableOAuth2AutoRegister bool - OAuth2UseNickname bool + RegisterEmailConfirm bool + OpenIDConnectScopes []string + EnableAutoRegistration bool + UseNickname bool } func newService() { @@ -120,14 +120,14 @@ func newService() { } sec = Cfg.Section("oauth2_client") - OAuth2Client.OAuth2RegisterEmailConfirm = sec.Key("OAUTH2_REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) - pats = sec.Key("OAUTH2_OPENID_CONNECT_SCOPES").Strings(" ") - OAuth2Client.OAuth2OpenIDConnectScopes = make([]string, 0, len(pats)) + OAuth2Client.RegisterEmailConfirm = sec.Key("REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) + pats = sec.Key("OPENID_CONNECT_SCOPES").Strings(" ") + OAuth2Client.OpenIDConnectScopes = make([]string, 0, len(pats)) for _, scope := range pats { if scope != "" { - OAuth2Client.OAuth2OpenIDConnectScopes = append(OAuth2Client.OAuth2OpenIDConnectScopes, scope) + OAuth2Client.OpenIDConnectScopes = append(OAuth2Client.OpenIDConnectScopes, scope) } } - OAuth2Client.EnableOAuth2AutoRegister = sec.Key("ENABLE_OAUTH2_AUTO_REGISTRATION").MustBool() - OAuth2Client.OAuth2UseNickname = sec.Key("OAUTH2_USE_NICKNAME").MustBool() + OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() + OAuth2Client.UseNickname = sec.Key("USE_NICKNAME").MustBool() } diff --git a/routers/user/auth.go b/routers/user/auth.go index 938c4c797fb17..98b1eb96fbd8c 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -588,10 +588,10 @@ func SignInOAuthCallback(ctx *context.Context) { } if u == nil { - if setting.OAuth2Client.EnableOAuth2AutoRegister { + if setting.OAuth2Client.EnableAutoRegistration { // create new user with details from oauth2 provider var name string - if setting.OAuth2Client.OAuth2UseNickname { + if setting.OAuth2Client.UseNickname { name = gothUser.NickName } else { name = gothUser.UserID @@ -600,7 +600,7 @@ func SignInOAuthCallback(ctx *context.Context) { Name: name, FullName: gothUser.Name, Email: gothUser.Email, - IsActive: !setting.OAuth2Client.OAuth2RegisterEmailConfirm, + IsActive: !setting.OAuth2Client.RegisterEmailConfirm, LoginType: models.LoginOAuth2, LoginSource: loginSource.ID, LoginName: gothUser.UserID, From 7a8478940741f63db9d7f08332bd70a78be2f818 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Wed, 1 Apr 2020 05:50:36 +0200 Subject: [PATCH 09/27] Uncommented setting in app.ini.sample Signed-off-by: Martin Michaelis Co-Authored-By: 6543 <6543@obermui.de> --- custom/conf/app.ini.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index ebab6022ff456..24f99cc2fc38f 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -505,7 +505,7 @@ BLACKLISTED_URIS = [oauth2_client] ; Whether a new auto registered oauth2 user needs to confirm their email. ; Do not include to use the REGISTER_EMAIL_CONFIRM setting from the `[service]` section. -;REGISTER_EMAIL_CONFIRM = +REGISTER_EMAIL_CONFIRM = ; Scopes for the openid connect oauth2 provider (seperated by space, the openid scope is implicitly added). ; Typical values are profile and email. ; For more information about the possible values see https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims From 6fe0f3950b754e4e6594e8422323e0e2a20ec20d Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Tue, 26 Jan 2021 23:05:37 +0100 Subject: [PATCH 10/27] fix conflict resolve relict --- routers/user/auth_openid.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/routers/user/auth_openid.go b/routers/user/auth_openid.go index 8691387c8244d..00ae6c7d000ca 100644 --- a/routers/user/auth_openid.go +++ b/routers/user/auth_openid.go @@ -18,9 +18,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/recaptcha" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/web" - "code.gitea.io/gitea/services/mailer" ) const ( From 9d1e1f2ed029b4b5d00442b16ac237de4da2a059 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Sun, 7 Feb 2021 09:26:31 +0000 Subject: [PATCH 11/27] Added error for missing fields in OAuth2 response Signed-off-by: Martin Michaelis --- routers/user/auth.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/routers/user/auth.go b/routers/user/auth.go index 06f952854c5a6..b5d37fa486fda 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -617,11 +617,27 @@ func SignInOAuthCallback(ctx *context.Context) { if setting.OAuth2Client.EnableAutoRegistration { // create new user with details from oauth2 provider var name string + var missingFields []string + if gothUser.UserID == "" { + missingFields = append(missingFields, "sub") + } + if gothUser.Email == "" { + missingFields = append(missingFields, "email") + } if setting.OAuth2Client.UseNickname { + if gothUser.NickName == "" { + missingFields = append(missingFields, "nickname") + } name = gothUser.NickName } else { name = gothUser.UserID } + if len(missingFields) > 0 { + log.Error("OAuth2 Provider %s returned empty or missing fields: %s", loginSource.Name, missingFields) + err = fmt.Errorf("OAuth2 Provider %s returned empty or missing fields: %s", loginSource.Name, missingFields) + ctx.ServerError("CreateUser", err) + return + } u = &models.User{ Name: name, FullName: gothUser.Name, From 8a64dfec60a7a312ee216f01463637aeafccced3 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Sun, 7 Feb 2021 15:11:25 +0000 Subject: [PATCH 12/27] Fixed error handling in createUserInContext Signed-off-by: Martin Michaelis --- routers/user/auth.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/routers/user/auth.go b/routers/user/auth.go index b5d37fa486fda..f531150450184 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -1188,22 +1188,22 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{ switch { case models.IsErrUserAlreadyExist(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tpl, &form) + ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tpl, form) case models.IsErrEmailAlreadyUsed(err): ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, &form) + ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, form) case models.IsErrEmailInvalid(err): ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, &form) + ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form) case models.IsErrNameReserved(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, &form) + ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(models.ErrNameReserved).Name), tpl, form) case models.IsErrNamePatternNotAllowed(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, &form) + ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form) case models.IsErrNameCharsNotAllowed(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", err.(models.ErrNameCharsNotAllowed).Name), tpl, &form) + ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", err.(models.ErrNameCharsNotAllowed).Name), tpl, form) default: ctx.ServerError("CreateUser", err) } From 3edad748acf1d22effe674f3a5a3d9f70495a6ce Mon Sep 17 00:00:00 2001 From: Viktor Kuzmin Date: Fri, 26 Mar 2021 14:36:59 +0300 Subject: [PATCH 13/27] Linking and auto linking on oauth2 registration --- custom/conf/app.example.ini | 13 ++ .../doc/advanced/config-cheat-sheet.en-us.md | 5 + models/migrations/migrations.go | 2 + models/migrations/v178.go | 22 ++++ modules/auth/oauth2/oauth2.go | 2 +- modules/setting/service.go | 36 ++++-- routers/user/auth.go | 119 +++++++++++++----- routers/user/auth_openid.go | 2 +- 8 files changed, 158 insertions(+), 43 deletions(-) create mode 100644 models/migrations/v178.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 9615cf25c1304..044f81ec30449 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -621,10 +621,23 @@ REGISTER_EMAIL_CONFIRM = ; Typical values are profile and email. ; For more information about the possible values see https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims OPENID_CONNECT_SCOPES = +; Scopes for the google oauth2 provider (seperated by space) +; Default scopes will provide only email, but for registration we may need more: email, profile +GOOGLE_CONNECT_SCOPES = ; Automatically create user accounts for new oauth2 users. ENABLE_AUTO_REGISTRATION = false ; Use the nickname attribute from the oauth2 provider instead of the userid as the new username. USE_NICKNAME = false +; Use the username part of email attribute from oauth2 provider instead of userid. +; This is useful for google ouath2 provider. +USE_EMAIL = false +; Update avatar if available from oauth2 provider. +; Update will be performed on each login. +UPDATE_AVATAR = false +; If true, show account linking login in case account already exist. +SHOW_ACCOUNT_LINKING_LOGIN = false +; If true, automatically link with existing account. +ENABLE_AUTO_REGISTRATION_LINKING = false [service] ; Time limit to confirm account/email registration diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 1fe2380cb4b12..a2e3fb07fcad7 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -429,8 +429,13 @@ relation to port exhaustion. - `REGISTER_EMAIL_CONFIRM`: *[service]* **REGISTER\_EMAIL\_CONFIRM**: Set this to enable or disable email confirmation of OAuth2 auto-registration. (Overwrites the REGISTER\_EMAIL\_CONFIRM setting of the `[service]` section) - `OPENID_CONNECT_SCOPES`: **\**: List of additional openid connect scopes. (`openid` is implicitly added) +- `GOOGLE_CONNECT_SCOPES`: **\**: List of additional google scopes (we may need to write here `email profile`) - `ENABLE_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration for oauth2 authentication. - `USE_NICKNAME`: **false**: Set this to use the nickname from the oauth2 provider instead of the userid for the username of the new user. +- `USE_EMAIL`: **false**: Set this to use username part of email from the oauth2 provider instead of the userid for the username of te new user (useful for google ouath2 provider). +- `UPDATE_AVATAR`: **false**: Set this to update user avatar if available from the oauth2 provider. +- `SHOW_ACCOUNT_LINKING_LOGIN`: **false**: Set this to show account linking login in case account already exist. +- `ENABLE_AUTO_REGISTRATION_LINKING`: **false**: Set this to automatically link with existing account. ## Service (`service`) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index c8e687b382eca..f484c2f1c6dc8 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -302,6 +302,8 @@ var migrations = []Migration{ NewMigration("Remove invalid labels from comments", removeInvalidLabels), // v177 -> v178 NewMigration("Delete orphaned IssueLabels", deleteOrphanedIssueLabels), + // v178 -> v179 + NewMigration("Convert avatar url to text", convertAvatarUrlToText), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v178.go b/models/migrations/v178.go new file mode 100644 index 0000000000000..cecb7f123863a --- /dev/null +++ b/models/migrations/v178.go @@ -0,0 +1,22 @@ +package migrations + +import ( + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +func convertAvatarUrlToText(x *xorm.Engine) error { + dbType := x.Dialect().URI().DBType + if dbType == schemas.SQLITE { // For SQLITE, varchar or char will always be represented as TEXT + return nil + } + + // Some oauth2 providers may give very long avatar urls (i.e. Google) + return modifyColumn(x, "external_login_user", &schemas.Column{ + Name: "avatar_url", + SQLType: schemas.SQLType{ + Name: schemas.Text, + }, + Nullable: true, + }) +} diff --git a/modules/auth/oauth2/oauth2.go b/modules/auth/oauth2/oauth2.go index 56369786d7f43..dac6e625e3b52 100644 --- a/modules/auth/oauth2/oauth2.go +++ b/modules/auth/oauth2/oauth2.go @@ -175,7 +175,7 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo } provider = gitlab.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, "read_user") case "gplus": // named gplus due to legacy gplus -> google migration (Google killed Google+). This ensures old connections still work - provider = google.New(clientID, clientSecret, callbackURL) + provider = google.New(clientID, clientSecret, callbackURL, setting.OAuth2Client.GoogleConnectScopes...) case "openidConnect": if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL, setting.OAuth2Client.OpenIDConnectScopes...); err != nil { log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, openIDConnectAutoDiscoveryURL, err) diff --git a/modules/setting/service.go b/modules/setting/service.go index 204419bbcdf58..27d52773941dc 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -5,6 +5,7 @@ package setting import ( + "gopkg.in/ini.v1" "regexp" "time" @@ -70,10 +71,15 @@ var Service struct { // OAuth2Client settings var OAuth2Client struct { - RegisterEmailConfirm bool - OpenIDConnectScopes []string - EnableAutoRegistration bool - UseNickname bool + RegisterEmailConfirm bool + OpenIDConnectScopes []string + GoogleConnectScopes []string + EnableAutoRegistration bool + UseNickname bool + UseEmail bool + UpdateAvatar bool + AccountLinkingLogin bool + AutoRegistrationLinking bool } func newService() { @@ -147,13 +153,23 @@ func newService() { sec = Cfg.Section("oauth2_client") OAuth2Client.RegisterEmailConfirm = sec.Key("REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) - pats = sec.Key("OPENID_CONNECT_SCOPES").Strings(" ") - OAuth2Client.OpenIDConnectScopes = make([]string, 0, len(pats)) - for _, scope := range pats { + OAuth2Client.OpenIDConnectScopes = parseScopes(sec, "OPENID_CONNECT_SCOPES") + OAuth2Client.GoogleConnectScopes = parseScopes(sec, "GOOGLE_CONNECT_SCOPES") + OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() + OAuth2Client.UseNickname = sec.Key("USE_NICKNAME").MustBool() + OAuth2Client.UseEmail = sec.Key("USE_EMAIL").MustBool() + OAuth2Client.UpdateAvatar = sec.Key("UPDATE_AVATAR").MustBool() + OAuth2Client.AccountLinkingLogin = sec.Key("SHOW_ACCOUNT_LINKING_LOGIN").MustBool(true) + OAuth2Client.AutoRegistrationLinking = sec.Key("ENABLE_AUTO_REGISTRATION_LINKING").MustBool() +} + +func parseScopes(sec *ini.Section, name string) []string { + parts := sec.Key(name).Strings(" ") + scopes := make([]string, 0, len(parts)) + for _, scope := range parts { if scope != "" { - OAuth2Client.OpenIDConnectScopes = append(OAuth2Client.OpenIDConnectScopes, scope) + scopes = append(scopes, scope) } } - OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() - OAuth2Client.UseNickname = sec.Key("USE_NICKNAME").MustBool() + return scopes } diff --git a/routers/user/auth.go b/routers/user/auth.go index 42de8c0da5daa..bc57202dca49e 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -8,6 +8,7 @@ package user import ( "errors" "fmt" + "io" "net/http" "strings" @@ -617,7 +618,6 @@ func SignInOAuthCallback(ctx *context.Context) { if u == nil { if setting.OAuth2Client.EnableAutoRegistration { // create new user with details from oauth2 provider - var name string var missingFields []string if gothUser.UserID == "" { missingFields = append(missingFields, "sub") @@ -625,13 +625,8 @@ func SignInOAuthCallback(ctx *context.Context) { if gothUser.Email == "" { missingFields = append(missingFields, "email") } - if setting.OAuth2Client.UseNickname { - if gothUser.NickName == "" { - missingFields = append(missingFields, "nickname") - } - name = gothUser.NickName - } else { - name = gothUser.UserID + if !setting.OAuth2Client.UseEmail && setting.OAuth2Client.UseNickname && gothUser.NickName == "" { + missingFields = append(missingFields, "nickname") } if len(missingFields) > 0 { log.Error("OAuth2 Provider %s returned empty or missing fields: %s", loginSource.Name, missingFields) @@ -640,7 +635,7 @@ func SignInOAuthCallback(ctx *context.Context) { return } u = &models.User{ - Name: name, + Name: getUserName(&gothUser), FullName: gothUser.Name, Email: gothUser.Email, IsActive: !setting.OAuth2Client.RegisterEmailConfirm, @@ -649,27 +644,76 @@ func SignInOAuthCallback(ctx *context.Context) { LoginName: gothUser.UserID, } - if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, &gothUser) { + if ok, needLink := createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, &gothUser, setting.OAuth2Client.AccountLinkingLogin || setting.OAuth2Client.AutoRegistrationLinking); !ok { // error already handled + if needLink { + if setting.OAuth2Client.AutoRegistrationLinking { + var user *models.User + user = &models.User{Name: u.Name} + hasUser, err := models.GetUser(user) + if !hasUser || err != nil { + user = &models.User{Email: u.Email} + hasUser, err = models.GetUser(user) + if !hasUser || err != nil { + ctx.ServerError("UserLinkAccount", err) + return + } + } + + // TODO: probably we should respect user's choice... + linkAccount(ctx, user, gothUser, true) + } else { + showLinkingLogin(ctx, gothUser) + } + } return } } else { // no existing user is found, request attach or new account - if err := ctx.Session.Set("linkAccountGothUser", gothUser); err != nil { - log.Error("Error setting linkAccountGothUser in session: %v", err) - } - if err := ctx.Session.Release(); err != nil { - log.Error("Error storing session: %v", err) - } - ctx.Redirect(setting.AppSubURL + "/user/link_account") - return + showLinkingLogin(ctx, gothUser) } } handleOAuth2SignIn(ctx, u, gothUser) } +func getUserName(gothUser *goth.User) string { + if setting.OAuth2Client.UseEmail { + return strings.Split(gothUser.Email, "@")[0] + } else if setting.OAuth2Client.UseNickname { + return gothUser.NickName + } else { + return gothUser.UserID + } +} + +func showLinkingLogin(ctx *context.Context, gothUser goth.User) { + if err := ctx.Session.Set("linkAccountGothUser", gothUser); err != nil { + log.Error("Error setting linkAccountGothUser in session: %v", err) + } + if err := ctx.Session.Release(); err != nil { + log.Error("Error storing session: %v", err) + } + ctx.Redirect(setting.AppSubURL + "/user/link_account") + return +} + +func updateAvatarIfNeed(url string, u *models.User) { + if setting.OAuth2Client.UpdateAvatar && len(url) > 0 { + resp, err := http.Get(url) + // ignore any error + if err == nil && resp.StatusCode == http.StatusOK { + data, err := io.ReadAll(resp.Body) + if err == nil { + _ = u.UploadAvatar(data) + } + } + } +} + func handleOAuth2SignIn(ctx *context.Context, u *models.User, gothUser goth.User) { + updateAvatarIfNeed(gothUser.AvatarURL, u) + // If this user is enrolled in 2FA, we can't sign the user in just yet. // Instead, redirect them to the 2FA authentication page. _, err := models.GetTwoFactorByUID(u.ID) @@ -806,8 +850,9 @@ func LinkAccount(ctx *context.Context) { return } - uname := gothUser.(goth.User).NickName - email := gothUser.(goth.User).Email + gu, _ := gothUser.(goth.User) + uname := getUserName(&gu) + email := gu.Email ctx.Data["user_name"] = uname ctx.Data["email"] = email @@ -876,22 +921,28 @@ func LinkAccountPostSignIn(ctx *context.Context) { return } + linkAccount(ctx, u, gothUser.(goth.User), signInForm.Remember) +} + +func linkAccount(ctx *context.Context, u *models.User, gothUser goth.User, remember bool) { + updateAvatarIfNeed(gothUser.AvatarURL, u) + // If this user is enrolled in 2FA, we can't sign the user in just yet. // Instead, redirect them to the 2FA authentication page. - _, err = models.GetTwoFactorByUID(u.ID) + _, err := models.GetTwoFactorByUID(u.ID) if err != nil { if !models.IsErrTwoFactorNotEnrolled(err) { ctx.ServerError("UserLinkAccount", err) return } - err = externalaccount.LinkAccountToUser(u, gothUser.(goth.User)) + err = externalaccount.LinkAccountToUser(u, gothUser) if err != nil { ctx.ServerError("UserLinkAccount", err) return } - handleSignIn(ctx, u, signInForm.Remember) + handleSignIn(ctx, u, remember) return } @@ -899,7 +950,7 @@ func LinkAccountPostSignIn(ctx *context.Context) { if err := ctx.Session.Set("twofaUid", u.ID); err != nil { log.Error("Error setting twofaUid in session: %v", err) } - if err := ctx.Session.Set("twofaRemember", signInForm.Remember); err != nil { + if err := ctx.Session.Set("twofaRemember", remember); err != nil { log.Error("Error setting twofaRemember in session: %v", err) } if err := ctx.Session.Set("linkAccount", true); err != nil { @@ -1022,7 +1073,7 @@ func LinkAccountPostRegister(ctx *context.Context) { LoginName: gothUser.(goth.User).UserID, } - if !createAndHandleCreatedUser(ctx, tplLinkAccount, form, u, gothUser.(*goth.User)) { + if ok, _ := createAndHandleCreatedUser(ctx, tplLinkAccount, form, u, gothUser.(*goth.User), false); !ok { // error already handled return } @@ -1163,7 +1214,7 @@ func SignUpPost(ctx *context.Context) { IsActive: !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm), } - if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil) { + if ok, _ := createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, false); !ok { // error already handled return } @@ -1174,10 +1225,10 @@ func SignUpPost(ctx *context.Context) { // createAndHandleCreatedUser calls createUserInContext and // then handleUserCreated. -func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User, gothUser *goth.User) (ok bool) { - ok = createUserInContext(ctx, tpl, form, u) +func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User, gothUser *goth.User, canLink bool) (ok bool, needLink bool) { + ok, needLink = createUserInContext(ctx, tpl, form, u, canLink) if !ok { - return + return ok, needLink } ok = handleUserCreated(ctx, u, gothUser) return @@ -1185,8 +1236,14 @@ func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form int // createUserInContext creates a user and handles errors within a given context. // Optionally a template can be specified. -func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User) (ok bool) { +func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User, canLink bool) (ok bool, needLink bool) { if err := models.CreateUser(u); err != nil { + if canLink { + if models.IsErrUserAlreadyExist(err) || models.IsErrEmailAlreadyUsed(err) { + return false, true + } + } + // handle error without template if len(tpl) == 0 { ctx.ServerError("CreateUser", err) @@ -1219,7 +1276,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{ return } log.Trace("Account created: %s", u.Name) - return true + return true, false } // handleUserCreated does additional steps after a new user is created. diff --git a/routers/user/auth_openid.go b/routers/user/auth_openid.go index c32438b9a9f12..5e27d8467418a 100644 --- a/routers/user/auth_openid.go +++ b/routers/user/auth_openid.go @@ -415,7 +415,7 @@ func RegisterOpenIDPost(ctx *context.Context) { Passwd: password, IsActive: !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm), } - if !createUserInContext(ctx, tplSignUpOID, form, u) { + if ok, _ := createUserInContext(ctx, tplSignUpOID, form, u, false); !ok { // error already handled return } From 7a811af9471983ee385ad71c6fa718dd7a0c132f Mon Sep 17 00:00:00 2001 From: Viktor Kuzmin Date: Tue, 30 Mar 2021 11:29:20 +0300 Subject: [PATCH 14/27] Code cleanup --- custom/conf/app.example.ini | 21 ++--- .../doc/advanced/config-cheat-sheet.en-us.md | 8 +- modules/auth/oauth2/oauth2.go | 2 +- modules/setting/service.go | 52 ++++++++--- routers/user/auth.go | 86 +++++++++---------- routers/user/auth_openid.go | 2 +- 6 files changed, 97 insertions(+), 74 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 044f81ec30449..da7d5c328562f 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -623,21 +623,22 @@ REGISTER_EMAIL_CONFIRM = OPENID_CONNECT_SCOPES = ; Scopes for the google oauth2 provider (seperated by space) ; Default scopes will provide only email, but for registration we may need more: email, profile -GOOGLE_CONNECT_SCOPES = +GOOGLE_SCOPES = ; Automatically create user accounts for new oauth2 users. ENABLE_AUTO_REGISTRATION = false -; Use the nickname attribute from the oauth2 provider instead of the userid as the new username. -USE_NICKNAME = false -; Use the username part of email attribute from oauth2 provider instead of userid. -; This is useful for google ouath2 provider. -USE_EMAIL = false +; The source of the username for new oauth2 accounts: +; userid = use the userid / sub attribute +; nickname = use the nickname attribute +; email = use the username part of the email attribute +USERNAME = userid ; Update avatar if available from oauth2 provider. ; Update will be performed on each login. UPDATE_AVATAR = false -; If true, show account linking login in case account already exist. -SHOW_ACCOUNT_LINKING_LOGIN = false -; If true, automatically link with existing account. -ENABLE_AUTO_REGISTRATION_LINKING = false +; How to handle if an account / email already exists: +; disabled = show an error +; login = show an account linking login +; auto = link directly with the account +ACCOUNT_LINKING = disabled [service] ; Time limit to confirm account/email registration diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index a2e3fb07fcad7..ead8fe824c23a 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -429,13 +429,11 @@ relation to port exhaustion. - `REGISTER_EMAIL_CONFIRM`: *[service]* **REGISTER\_EMAIL\_CONFIRM**: Set this to enable or disable email confirmation of OAuth2 auto-registration. (Overwrites the REGISTER\_EMAIL\_CONFIRM setting of the `[service]` section) - `OPENID_CONNECT_SCOPES`: **\**: List of additional openid connect scopes. (`openid` is implicitly added) -- `GOOGLE_CONNECT_SCOPES`: **\**: List of additional google scopes (we may need to write here `email profile`) +- `GOOGLE_SCOPES`: **\**: List of additional google scopes (we may need to write here `email profile`) - `ENABLE_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration for oauth2 authentication. -- `USE_NICKNAME`: **false**: Set this to use the nickname from the oauth2 provider instead of the userid for the username of the new user. -- `USE_EMAIL`: **false**: Set this to use username part of email from the oauth2 provider instead of the userid for the username of te new user (useful for google ouath2 provider). +- `USERNAME`: **userid**: The source of the username for new oauth2 accounts: userid, nickname, email (username part). - `UPDATE_AVATAR`: **false**: Set this to update user avatar if available from the oauth2 provider. -- `SHOW_ACCOUNT_LINKING_LOGIN`: **false**: Set this to show account linking login in case account already exist. -- `ENABLE_AUTO_REGISTRATION_LINKING`: **false**: Set this to automatically link with existing account. +- `ACCOUNT_LINKING`: **disabled**: How to handle if an account / email already exists: disabled / login / auto. ## Service (`service`) diff --git a/modules/auth/oauth2/oauth2.go b/modules/auth/oauth2/oauth2.go index dac6e625e3b52..b7da3f80c6e60 100644 --- a/modules/auth/oauth2/oauth2.go +++ b/modules/auth/oauth2/oauth2.go @@ -175,7 +175,7 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo } provider = gitlab.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, "read_user") case "gplus": // named gplus due to legacy gplus -> google migration (Google killed Google+). This ensures old connections still work - provider = google.New(clientID, clientSecret, callbackURL, setting.OAuth2Client.GoogleConnectScopes...) + provider = google.New(clientID, clientSecret, callbackURL, setting.OAuth2Client.GoogleScopes...) case "openidConnect": if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL, setting.OAuth2Client.OpenIDConnectScopes...); err != nil { log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, openIDConnectAutoDiscoveryURL, err) diff --git a/modules/setting/service.go b/modules/setting/service.go index 27d52773941dc..e191dec8dba17 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -71,15 +71,13 @@ var Service struct { // OAuth2Client settings var OAuth2Client struct { - RegisterEmailConfirm bool - OpenIDConnectScopes []string - GoogleConnectScopes []string - EnableAutoRegistration bool - UseNickname bool - UseEmail bool - UpdateAvatar bool - AccountLinkingLogin bool - AutoRegistrationLinking bool + RegisterEmailConfirm bool + OpenIDConnectScopes []string + GoogleScopes []string + EnableAutoRegistration bool + Username string + UpdateAvatar bool + AccountLinking string } func newService() { @@ -154,13 +152,39 @@ func newService() { sec = Cfg.Section("oauth2_client") OAuth2Client.RegisterEmailConfirm = sec.Key("REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) OAuth2Client.OpenIDConnectScopes = parseScopes(sec, "OPENID_CONNECT_SCOPES") - OAuth2Client.GoogleConnectScopes = parseScopes(sec, "GOOGLE_CONNECT_SCOPES") + OAuth2Client.GoogleScopes = parseScopes(sec, "GOOGLE_SCOPES") OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() - OAuth2Client.UseNickname = sec.Key("USE_NICKNAME").MustBool() - OAuth2Client.UseEmail = sec.Key("USE_EMAIL").MustBool() + OAuth2Client.Username = sec.Key("USERNAME").MustString("userid") + if !isValidUsername(OAuth2Client.Username) { + log.Warn("Username setting is not valid: '%s', will fallback to 'userid'", OAuth2Client.Username) + OAuth2Client.Username = "userid" + } OAuth2Client.UpdateAvatar = sec.Key("UPDATE_AVATAR").MustBool() - OAuth2Client.AccountLinkingLogin = sec.Key("SHOW_ACCOUNT_LINKING_LOGIN").MustBool(true) - OAuth2Client.AutoRegistrationLinking = sec.Key("ENABLE_AUTO_REGISTRATION_LINKING").MustBool() + OAuth2Client.AccountLinking = sec.Key("ACCOUNT_LINKING").MustString("ACCOUNT_LINKING") + if !isValidAccountLinking(OAuth2Client.AccountLinking) { + log.Warn("Account linking setting is not valid: '%s', will fallback to 'disabled'") + OAuth2Client.AccountLinking = "disabled" + } +} + +func isValidUsername(username string) bool { + switch username { + case "userid": + case "nickname": + case "email": + return true + } + return false +} + +func isValidAccountLinking(accountLinking string) bool { + switch accountLinking { + case "disabled": + case "login": + case "auto": + return true + } + return false } func parseScopes(sec *ini.Section, name string) []string { diff --git a/routers/user/auth.go b/routers/user/auth.go index bc57202dca49e..b651745d1dc44 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -625,7 +625,7 @@ func SignInOAuthCallback(ctx *context.Context) { if gothUser.Email == "" { missingFields = append(missingFields, "email") } - if !setting.OAuth2Client.UseEmail && setting.OAuth2Client.UseNickname && gothUser.NickName == "" { + if setting.OAuth2Client.Username == "nickname" && gothUser.NickName == "" { missingFields = append(missingFields, "nickname") } if len(missingFields) > 0 { @@ -644,45 +644,25 @@ func SignInOAuthCallback(ctx *context.Context) { LoginName: gothUser.UserID, } - if ok, needLink := createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, &gothUser, setting.OAuth2Client.AccountLinkingLogin || setting.OAuth2Client.AutoRegistrationLinking); !ok { - // error already handled - if needLink { - if setting.OAuth2Client.AutoRegistrationLinking { - var user *models.User - user = &models.User{Name: u.Name} - hasUser, err := models.GetUser(user) - if !hasUser || err != nil { - user = &models.User{Email: u.Email} - hasUser, err = models.GetUser(user) - if !hasUser || err != nil { - ctx.ServerError("UserLinkAccount", err) - return - } - } - - // TODO: probably we should respect user's choice... - linkAccount(ctx, user, gothUser, true) - } else { - showLinkingLogin(ctx, gothUser) - } - } - return - } + createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, &gothUser, setting.OAuth2Client.AccountLinking != "disabled") } else { // no existing user is found, request attach or new account showLinkingLogin(ctx, gothUser) } + + return } handleOAuth2SignIn(ctx, u, gothUser) } func getUserName(gothUser *goth.User) string { - if setting.OAuth2Client.UseEmail { + switch setting.OAuth2Client.Username { + case "email": return strings.Split(gothUser.Email, "@")[0] - } else if setting.OAuth2Client.UseNickname { + case "nickname": return gothUser.NickName - } else { + default: // "userid" return gothUser.UserID } } @@ -701,10 +681,15 @@ func showLinkingLogin(ctx *context.Context, gothUser goth.User) { func updateAvatarIfNeed(url string, u *models.User) { if setting.OAuth2Client.UpdateAvatar && len(url) > 0 { resp, err := http.Get(url) + if err == nil { + defer func() { + _ = resp.Body.Close() + }() + } // ignore any error if err == nil && resp.StatusCode == http.StatusOK { - data, err := io.ReadAll(resp.Body) - if err == nil { + data, err := io.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize + 1)) + if err == nil && int64(len(data)) <= setting.Avatar.MaxFileSize { _ = u.UploadAvatar(data) } } @@ -1073,7 +1058,7 @@ func LinkAccountPostRegister(ctx *context.Context) { LoginName: gothUser.(goth.User).UserID, } - if ok, _ := createAndHandleCreatedUser(ctx, tplLinkAccount, form, u, gothUser.(*goth.User), false); !ok { + if !createAndHandleCreatedUser(ctx, tplLinkAccount, form, u, gothUser.(*goth.User), false) { // error already handled return } @@ -1214,7 +1199,7 @@ func SignUpPost(ctx *context.Context) { IsActive: !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm), } - if ok, _ := createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, false); !ok { + if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, false) { // error already handled return } @@ -1225,22 +1210,37 @@ func SignUpPost(ctx *context.Context) { // createAndHandleCreatedUser calls createUserInContext and // then handleUserCreated. -func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User, gothUser *goth.User, canLink bool) (ok bool, needLink bool) { - ok, needLink = createUserInContext(ctx, tpl, form, u, canLink) - if !ok { - return ok, needLink +func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User, gothUser *goth.User, allowLink bool) bool { + if !createUserInContext(ctx, tpl, form, u, gothUser, allowLink) { + return false } - ok = handleUserCreated(ctx, u, gothUser) - return + return handleUserCreated(ctx, u, gothUser) } // createUserInContext creates a user and handles errors within a given context. // Optionally a template can be specified. -func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User, canLink bool) (ok bool, needLink bool) { +func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User, gothUser *goth.User, allowLink bool) (ok bool) { if err := models.CreateUser(u); err != nil { - if canLink { - if models.IsErrUserAlreadyExist(err) || models.IsErrEmailAlreadyUsed(err) { - return false, true + if allowLink && (models.IsErrUserAlreadyExist(err) || models.IsErrEmailAlreadyUsed(err)) { + if setting.OAuth2Client.AccountLinking == "auto" { + var user *models.User + user = &models.User{Name: u.Name} + hasUser, err := models.GetUser(user) + if !hasUser || err != nil { + user = &models.User{Email: u.Email} + hasUser, err = models.GetUser(user) + if !hasUser || err != nil { + ctx.ServerError("UserLinkAccount", err) + return + } + } + + // TODO: probably we should respect 'remeber' user's choice... + linkAccount(ctx, user, *gothUser, true) + return // user is already created here, all redirects are handled + } else if setting.OAuth2Client.AccountLinking == "login" { + showLinkingLogin(ctx, *gothUser) + return // user will be created only after linking login } } @@ -1276,7 +1276,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{ return } log.Trace("Account created: %s", u.Name) - return true, false + return true } // handleUserCreated does additional steps after a new user is created. diff --git a/routers/user/auth_openid.go b/routers/user/auth_openid.go index 5e27d8467418a..daf3be17cc48b 100644 --- a/routers/user/auth_openid.go +++ b/routers/user/auth_openid.go @@ -415,7 +415,7 @@ func RegisterOpenIDPost(ctx *context.Context) { Passwd: password, IsActive: !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm), } - if ok, _ := createUserInContext(ctx, tplSignUpOID, form, u, false); !ok { + if !createUserInContext(ctx, tplSignUpOID, form, u, nil, false) { // error already handled return } From 150163e15cc49288f117ddcae7ae627606139692 Mon Sep 17 00:00:00 2001 From: Viktor Kuzmin Date: Tue, 30 Mar 2021 13:56:26 +0300 Subject: [PATCH 15/27] Fix lint problems --- models/migrations/migrations.go | 2 +- models/migrations/v178.go | 2 +- modules/setting/service.go | 4 ++-- routers/user/auth.go | 3 +-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index f484c2f1c6dc8..7d4da0d0fa375 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -303,7 +303,7 @@ var migrations = []Migration{ // v177 -> v178 NewMigration("Delete orphaned IssueLabels", deleteOrphanedIssueLabels), // v178 -> v179 - NewMigration("Convert avatar url to text", convertAvatarUrlToText), + NewMigration("Convert avatar url to text", convertAvatarURLToText), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v178.go b/models/migrations/v178.go index cecb7f123863a..206c15590f013 100644 --- a/models/migrations/v178.go +++ b/models/migrations/v178.go @@ -5,7 +5,7 @@ import ( "xorm.io/xorm/schemas" ) -func convertAvatarUrlToText(x *xorm.Engine) error { +func convertAvatarURLToText(x *xorm.Engine) error { dbType := x.Dialect().URI().DBType if dbType == schemas.SQLITE { // For SQLITE, varchar or char will always be represented as TEXT return nil diff --git a/modules/setting/service.go b/modules/setting/service.go index e191dec8dba17..593d30aa17bd2 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -5,12 +5,12 @@ package setting import ( - "gopkg.in/ini.v1" "regexp" "time" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" + "gopkg.in/ini.v1" ) // Service settings @@ -190,7 +190,7 @@ func isValidAccountLinking(accountLinking string) bool { func parseScopes(sec *ini.Section, name string) []string { parts := sec.Key(name).Strings(" ") scopes := make([]string, 0, len(parts)) - for _, scope := range parts { + for _, scope := range parts { if scope != "" { scopes = append(scopes, scope) } diff --git a/routers/user/auth.go b/routers/user/auth.go index b651745d1dc44..fd4496a4226ce 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -675,7 +675,6 @@ func showLinkingLogin(ctx *context.Context, gothUser goth.User) { log.Error("Error storing session: %v", err) } ctx.Redirect(setting.AppSubURL + "/user/link_account") - return } func updateAvatarIfNeed(url string, u *models.User) { @@ -688,7 +687,7 @@ func updateAvatarIfNeed(url string, u *models.User) { } // ignore any error if err == nil && resp.StatusCode == http.StatusOK { - data, err := io.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize + 1)) + data, err := io.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize+1)) if err == nil && int64(len(data)) <= setting.Avatar.MaxFileSize { _ = u.UploadAvatar(data) } From 586dff7eeeaf5e46c47ed80c1bbf50525e42c6a3 Mon Sep 17 00:00:00 2001 From: Viktor Kuzmin Date: Tue, 30 Mar 2021 14:37:06 +0300 Subject: [PATCH 16/27] Fix bugs in validating config options --- modules/setting/service.go | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/modules/setting/service.go b/modules/setting/service.go index 593d30aa17bd2..063174a8245a2 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -155,34 +155,23 @@ func newService() { OAuth2Client.GoogleScopes = parseScopes(sec, "GOOGLE_SCOPES") OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() OAuth2Client.Username = sec.Key("USERNAME").MustString("userid") - if !isValidUsername(OAuth2Client.Username) { + if !isInSet(OAuth2Client.Username, []string{"userid", "nickname", "email"}) { log.Warn("Username setting is not valid: '%s', will fallback to 'userid'", OAuth2Client.Username) OAuth2Client.Username = "userid" } OAuth2Client.UpdateAvatar = sec.Key("UPDATE_AVATAR").MustBool() - OAuth2Client.AccountLinking = sec.Key("ACCOUNT_LINKING").MustString("ACCOUNT_LINKING") - if !isValidAccountLinking(OAuth2Client.AccountLinking) { - log.Warn("Account linking setting is not valid: '%s', will fallback to 'disabled'") + OAuth2Client.AccountLinking = sec.Key("ACCOUNT_LINKING").MustString("disabled") + if !isInSet(OAuth2Client.AccountLinking, []string{"disabled", "login", "auto"}) { + log.Warn("Account linking setting is not valid: '%s', will fallback to 'disabled'", OAuth2Client.AccountLinking) OAuth2Client.AccountLinking = "disabled" } } -func isValidUsername(username string) bool { - switch username { - case "userid": - case "nickname": - case "email": - return true - } - return false -} - -func isValidAccountLinking(accountLinking string) bool { - switch accountLinking { - case "disabled": - case "login": - case "auto": - return true +func isInSet(value string, set []string) bool { + for _, v := range set { + if value == v { + return true + } } return false } From fb464dbeb74dd3dffc6c8ac63fef0976201a7932 Mon Sep 17 00:00:00 2001 From: Viktor Kuzmin Date: Tue, 30 Mar 2021 21:09:35 +0300 Subject: [PATCH 17/27] Convert oauth2 client types to string enums --- modules/setting/service.go | 61 ++++++++++++++++++++++++++++---------- routers/user/auth.go | 14 ++++----- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/modules/setting/service.go b/modules/setting/service.go index 063174a8245a2..47f84ebdf66a4 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -69,15 +69,39 @@ var Service struct { } `ini:"service.explore"` } +// OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data +type OAuth2UsernameType string + +const ( + // OAuth2UsernameUserid oauth2 userid field will be used as gitea name + OAuth2UsernameUserid OAuth2UsernameType = "userid" + // OAuth2UsernameNickname oauth2 nickname field will be used as gitea name + OAuth2UsernameNickname OAuth2UsernameType = "nickname" + // OAuth2UsernameEmail username of oauth2 email filed will be used as gitea name + OAuth2UsernameEmail OAuth2UsernameType = "email" +) + +// OAuth2AccountLinkingType is enum describing behaviour of linking with existing account +type OAuth2AccountLinkingType string + +const ( + // OAuth2AccountLinkingDisabled error will be displayed if account exist + OAuth2AccountLinkingDisabled OAuth2AccountLinkingType = "disabled" + // OAuth2AccountLinkingLogin account linking login will be displayed if account exist + OAuth2AccountLinkingLogin OAuth2AccountLinkingType = "login" + // OAuth2AccountLinkingAuto account will be automatically linked if account exist + OAuth2AccountLinkingAuto OAuth2AccountLinkingType = "auto" +) + // OAuth2Client settings var OAuth2Client struct { RegisterEmailConfirm bool OpenIDConnectScopes []string GoogleScopes []string EnableAutoRegistration bool - Username string + Username OAuth2UsernameType UpdateAvatar bool - AccountLinking string + AccountLinking OAuth2AccountLinkingType } func newService() { @@ -154,24 +178,31 @@ func newService() { OAuth2Client.OpenIDConnectScopes = parseScopes(sec, "OPENID_CONNECT_SCOPES") OAuth2Client.GoogleScopes = parseScopes(sec, "GOOGLE_SCOPES") OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() - OAuth2Client.Username = sec.Key("USERNAME").MustString("userid") - if !isInSet(OAuth2Client.Username, []string{"userid", "nickname", "email"}) { - log.Warn("Username setting is not valid: '%s', will fallback to 'userid'", OAuth2Client.Username) - OAuth2Client.Username = "userid" + OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameUserid))) + if !OAuth2Client.Username.isValid() { + log.Warn("Username setting is not valid: '%s', will fallback to '%s'", OAuth2Client.Username, OAuth2UsernameUserid) + OAuth2Client.Username = OAuth2UsernameUserid } OAuth2Client.UpdateAvatar = sec.Key("UPDATE_AVATAR").MustBool() - OAuth2Client.AccountLinking = sec.Key("ACCOUNT_LINKING").MustString("disabled") - if !isInSet(OAuth2Client.AccountLinking, []string{"disabled", "login", "auto"}) { - log.Warn("Account linking setting is not valid: '%s', will fallback to 'disabled'", OAuth2Client.AccountLinking) - OAuth2Client.AccountLinking = "disabled" + OAuth2Client.AccountLinking = OAuth2AccountLinkingType(sec.Key("ACCOUNT_LINKING").MustString(string(OAuth2AccountLinkingDisabled))) + if !OAuth2Client.AccountLinking.isValid() { + log.Warn("Account linking setting is not valid: '%s', will fallback to '%s'", OAuth2Client.AccountLinking, OAuth2AccountLinkingDisabled) + OAuth2Client.AccountLinking = OAuth2AccountLinkingDisabled } } -func isInSet(value string, set []string) bool { - for _, v := range set { - if value == v { - return true - } +func (username OAuth2UsernameType) isValid() bool { + switch username { + case OAuth2UsernameUserid, OAuth2UsernameNickname, OAuth2UsernameEmail: + return true + } + return false +} + +func (accountLinking OAuth2AccountLinkingType) isValid() bool { + switch accountLinking { + case OAuth2AccountLinkingDisabled, OAuth2AccountLinkingLogin, OAuth2AccountLinkingAuto: + return true } return false } diff --git a/routers/user/auth.go b/routers/user/auth.go index fd4496a4226ce..98227ac54ad8e 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -625,7 +625,7 @@ func SignInOAuthCallback(ctx *context.Context) { if gothUser.Email == "" { missingFields = append(missingFields, "email") } - if setting.OAuth2Client.Username == "nickname" && gothUser.NickName == "" { + if setting.OAuth2Client.Username == setting.OAuth2UsernameNickname && gothUser.NickName == "" { missingFields = append(missingFields, "nickname") } if len(missingFields) > 0 { @@ -644,7 +644,7 @@ func SignInOAuthCallback(ctx *context.Context) { LoginName: gothUser.UserID, } - createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, &gothUser, setting.OAuth2Client.AccountLinking != "disabled") + createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) } else { // no existing user is found, request attach or new account showLinkingLogin(ctx, gothUser) @@ -658,11 +658,11 @@ func SignInOAuthCallback(ctx *context.Context) { func getUserName(gothUser *goth.User) string { switch setting.OAuth2Client.Username { - case "email": + case setting.OAuth2UsernameEmail: return strings.Split(gothUser.Email, "@")[0] - case "nickname": + case setting.OAuth2UsernameNickname: return gothUser.NickName - default: // "userid" + default: // OAuth2UsernameUserid return gothUser.UserID } } @@ -1221,7 +1221,7 @@ func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form int func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{}, u *models.User, gothUser *goth.User, allowLink bool) (ok bool) { if err := models.CreateUser(u); err != nil { if allowLink && (models.IsErrUserAlreadyExist(err) || models.IsErrEmailAlreadyUsed(err)) { - if setting.OAuth2Client.AccountLinking == "auto" { + if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto { var user *models.User user = &models.User{Name: u.Name} hasUser, err := models.GetUser(user) @@ -1237,7 +1237,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{ // TODO: probably we should respect 'remeber' user's choice... linkAccount(ctx, user, *gothUser, true) return // user is already created here, all redirects are handled - } else if setting.OAuth2Client.AccountLinking == "login" { + } else if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingLogin { showLinkingLogin(ctx, *gothUser) return // user will be created only after linking login } From d62cf151400cb4897b8dcf9422fd942326a48888 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Wed, 31 Mar 2021 01:32:53 +0200 Subject: [PATCH 18/27] Fix ioutil.ReadAll --- routers/user/auth.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/routers/user/auth.go b/routers/user/auth.go index 98227ac54ad8e..2fdce58e4b10e 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "io/ioutil" "net/http" "strings" @@ -687,7 +688,7 @@ func updateAvatarIfNeed(url string, u *models.User) { } // ignore any error if err == nil && resp.StatusCode == http.StatusOK { - data, err := io.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize+1)) + data, err := ioutil.ReadAll(io.LimitReader(resp.Body, setting.Avatar.MaxFileSize+1)) if err == nil && int64(len(data)) <= setting.Avatar.MaxFileSize { _ = u.UploadAvatar(data) } From f4924a3f0714e71bd54afbf9cdec32ccc6403639 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Wed, 31 Mar 2021 12:12:14 +0200 Subject: [PATCH 19/27] Set default username source to nickname --- custom/conf/app.example.ini | 2 +- docs/content/doc/advanced/config-cheat-sheet.en-us.md | 2 +- modules/setting/service.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index da7d5c328562f..4ac756553f51c 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -630,7 +630,7 @@ ENABLE_AUTO_REGISTRATION = false ; userid = use the userid / sub attribute ; nickname = use the nickname attribute ; email = use the username part of the email attribute -USERNAME = userid +USERNAME = nickname ; Update avatar if available from oauth2 provider. ; Update will be performed on each login. UPDATE_AVATAR = false diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index ead8fe824c23a..a179d300b41d7 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -431,7 +431,7 @@ relation to port exhaustion. - `OPENID_CONNECT_SCOPES`: **\**: List of additional openid connect scopes. (`openid` is implicitly added) - `GOOGLE_SCOPES`: **\**: List of additional google scopes (we may need to write here `email profile`) - `ENABLE_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration for oauth2 authentication. -- `USERNAME`: **userid**: The source of the username for new oauth2 accounts: userid, nickname, email (username part). +- `USERNAME`: **nickname**: The source of the username for new oauth2 accounts: userid, nickname, email (username part). - `UPDATE_AVATAR`: **false**: Set this to update user avatar if available from the oauth2 provider. - `ACCOUNT_LINKING`: **disabled**: How to handle if an account / email already exists: disabled / login / auto. diff --git a/modules/setting/service.go b/modules/setting/service.go index 47f84ebdf66a4..1eb2d2f4f4c26 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -178,10 +178,10 @@ func newService() { OAuth2Client.OpenIDConnectScopes = parseScopes(sec, "OPENID_CONNECT_SCOPES") OAuth2Client.GoogleScopes = parseScopes(sec, "GOOGLE_SCOPES") OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() - OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameUserid))) + OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameNickname))) if !OAuth2Client.Username.isValid() { - log.Warn("Username setting is not valid: '%s', will fallback to '%s'", OAuth2Client.Username, OAuth2UsernameUserid) - OAuth2Client.Username = OAuth2UsernameUserid + log.Warn("Username setting is not valid: '%s', will fallback to '%s'", OAuth2Client.Username, OAuth2UsernameNickname) + OAuth2Client.Username = OAuth2UsernameNickname } OAuth2Client.UpdateAvatar = sec.Key("UPDATE_AVATAR").MustBool() OAuth2Client.AccountLinking = OAuth2AccountLinkingType(sec.Key("ACCOUNT_LINKING").MustString(string(OAuth2AccountLinkingDisabled))) From 491610a9903ec3c2cd2838076796a734fcfb8955 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Thu, 1 Apr 2021 15:04:25 +0200 Subject: [PATCH 20/27] Add copyright and empty line --- models/migrations/v178.go | 4 ++++ modules/setting/service.go | 1 + 2 files changed, 5 insertions(+) diff --git a/models/migrations/v178.go b/models/migrations/v178.go index 206c15590f013..735e6b62dd2c5 100644 --- a/models/migrations/v178.go +++ b/models/migrations/v178.go @@ -1,3 +1,7 @@ +// 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 migrations import ( diff --git a/modules/setting/service.go b/modules/setting/service.go index 1eb2d2f4f4c26..0452c45681206 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" + "gopkg.in/ini.v1" ) From cd7241e57dd70d5dad14b685c2f4c83a2290b828 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Sat, 3 Apr 2021 08:01:42 +0000 Subject: [PATCH 21/27] Move oauth2 client settings to new file --- modules/setting/oauth2_client.go | 92 ++++++++++++++++++++++++++++++++ modules/setting/service.go | 81 ---------------------------- modules/setting/setting.go | 1 + 3 files changed, 93 insertions(+), 81 deletions(-) create mode 100644 modules/setting/oauth2_client.go diff --git a/modules/setting/oauth2_client.go b/modules/setting/oauth2_client.go new file mode 100644 index 0000000000000..56ad361bb39d4 --- /dev/null +++ b/modules/setting/oauth2_client.go @@ -0,0 +1,92 @@ +// 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 setting + +import ( + "code.gitea.io/gitea/modules/log" + + "gopkg.in/ini.v1" +) + +// OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data +type OAuth2UsernameType string + +const ( + // OAuth2UsernameUserid oauth2 userid field will be used as gitea name + OAuth2UsernameUserid OAuth2UsernameType = "userid" + // OAuth2UsernameNickname oauth2 nickname field will be used as gitea name + OAuth2UsernameNickname OAuth2UsernameType = "nickname" + // OAuth2UsernameEmail username of oauth2 email filed will be used as gitea name + OAuth2UsernameEmail OAuth2UsernameType = "email" +) + +func (username OAuth2UsernameType) isValid() bool { + switch username { + case OAuth2UsernameUserid, OAuth2UsernameNickname, OAuth2UsernameEmail: + return true + } + return false +} + +// OAuth2AccountLinkingType is enum describing behaviour of linking with existing account +type OAuth2AccountLinkingType string + +const ( + // OAuth2AccountLinkingDisabled error will be displayed if account exist + OAuth2AccountLinkingDisabled OAuth2AccountLinkingType = "disabled" + // OAuth2AccountLinkingLogin account linking login will be displayed if account exist + OAuth2AccountLinkingLogin OAuth2AccountLinkingType = "login" + // OAuth2AccountLinkingAuto account will be automatically linked if account exist + OAuth2AccountLinkingAuto OAuth2AccountLinkingType = "auto" +) + +func (accountLinking OAuth2AccountLinkingType) isValid() bool { + switch accountLinking { + case OAuth2AccountLinkingDisabled, OAuth2AccountLinkingLogin, OAuth2AccountLinkingAuto: + return true + } + return false +} + +// OAuth2Client settings +var OAuth2Client struct { + RegisterEmailConfirm bool + OpenIDConnectScopes []string + GoogleScopes []string + EnableAutoRegistration bool + Username OAuth2UsernameType + UpdateAvatar bool + AccountLinking OAuth2AccountLinkingType +} + +func newOAuth2Client() { + sec := Cfg.Section("oauth2_client") + OAuth2Client.RegisterEmailConfirm = sec.Key("REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) + OAuth2Client.OpenIDConnectScopes = parseScopes(sec, "OPENID_CONNECT_SCOPES") + OAuth2Client.GoogleScopes = parseScopes(sec, "GOOGLE_SCOPES") + OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() + OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameNickname))) + if !OAuth2Client.Username.isValid() { + log.Warn("Username setting is not valid: '%s', will fallback to '%s'", OAuth2Client.Username, OAuth2UsernameNickname) + OAuth2Client.Username = OAuth2UsernameNickname + } + OAuth2Client.UpdateAvatar = sec.Key("UPDATE_AVATAR").MustBool() + OAuth2Client.AccountLinking = OAuth2AccountLinkingType(sec.Key("ACCOUNT_LINKING").MustString(string(OAuth2AccountLinkingDisabled))) + if !OAuth2Client.AccountLinking.isValid() { + log.Warn("Account linking setting is not valid: '%s', will fallback to '%s'", OAuth2Client.AccountLinking, OAuth2AccountLinkingDisabled) + OAuth2Client.AccountLinking = OAuth2AccountLinkingDisabled + } +} + +func parseScopes(sec *ini.Section, name string) []string { + parts := sec.Key(name).Strings(" ") + scopes := make([]string, 0, len(parts)) + for _, scope := range parts { + if scope != "" { + scopes = append(scopes, scope) + } + } + return scopes +} diff --git a/modules/setting/service.go b/modules/setting/service.go index 0452c45681206..9696e98641837 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -10,8 +10,6 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" - - "gopkg.in/ini.v1" ) // Service settings @@ -70,41 +68,6 @@ var Service struct { } `ini:"service.explore"` } -// OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data -type OAuth2UsernameType string - -const ( - // OAuth2UsernameUserid oauth2 userid field will be used as gitea name - OAuth2UsernameUserid OAuth2UsernameType = "userid" - // OAuth2UsernameNickname oauth2 nickname field will be used as gitea name - OAuth2UsernameNickname OAuth2UsernameType = "nickname" - // OAuth2UsernameEmail username of oauth2 email filed will be used as gitea name - OAuth2UsernameEmail OAuth2UsernameType = "email" -) - -// OAuth2AccountLinkingType is enum describing behaviour of linking with existing account -type OAuth2AccountLinkingType string - -const ( - // OAuth2AccountLinkingDisabled error will be displayed if account exist - OAuth2AccountLinkingDisabled OAuth2AccountLinkingType = "disabled" - // OAuth2AccountLinkingLogin account linking login will be displayed if account exist - OAuth2AccountLinkingLogin OAuth2AccountLinkingType = "login" - // OAuth2AccountLinkingAuto account will be automatically linked if account exist - OAuth2AccountLinkingAuto OAuth2AccountLinkingType = "auto" -) - -// OAuth2Client settings -var OAuth2Client struct { - RegisterEmailConfirm bool - OpenIDConnectScopes []string - GoogleScopes []string - EnableAutoRegistration bool - Username OAuth2UsernameType - UpdateAvatar bool - AccountLinking OAuth2AccountLinkingType -} - func newService() { sec := Cfg.Section("service") Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180) @@ -173,48 +136,4 @@ func newService() { Service.OpenIDBlacklist[i] = regexp.MustCompilePOSIX(p) } } - - sec = Cfg.Section("oauth2_client") - OAuth2Client.RegisterEmailConfirm = sec.Key("REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) - OAuth2Client.OpenIDConnectScopes = parseScopes(sec, "OPENID_CONNECT_SCOPES") - OAuth2Client.GoogleScopes = parseScopes(sec, "GOOGLE_SCOPES") - OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() - OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameNickname))) - if !OAuth2Client.Username.isValid() { - log.Warn("Username setting is not valid: '%s', will fallback to '%s'", OAuth2Client.Username, OAuth2UsernameNickname) - OAuth2Client.Username = OAuth2UsernameNickname - } - OAuth2Client.UpdateAvatar = sec.Key("UPDATE_AVATAR").MustBool() - OAuth2Client.AccountLinking = OAuth2AccountLinkingType(sec.Key("ACCOUNT_LINKING").MustString(string(OAuth2AccountLinkingDisabled))) - if !OAuth2Client.AccountLinking.isValid() { - log.Warn("Account linking setting is not valid: '%s', will fallback to '%s'", OAuth2Client.AccountLinking, OAuth2AccountLinkingDisabled) - OAuth2Client.AccountLinking = OAuth2AccountLinkingDisabled - } -} - -func (username OAuth2UsernameType) isValid() bool { - switch username { - case OAuth2UsernameUserid, OAuth2UsernameNickname, OAuth2UsernameEmail: - return true - } - return false -} - -func (accountLinking OAuth2AccountLinkingType) isValid() bool { - switch accountLinking { - case OAuth2AccountLinkingDisabled, OAuth2AccountLinkingLogin, OAuth2AccountLinkingAuto: - return true - } - return false -} - -func parseScopes(sec *ini.Section, name string) []string { - parts := sec.Key(name).Strings(" ") - scopes := make([]string, 0, len(parts)) - for _, scope := range parts { - if scope != "" { - scopes = append(scopes, scope) - } - } - return scopes } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 280987ed66e40..0711eb580c368 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1168,6 +1168,7 @@ func MakeManifestData(appName string, appURL string, absoluteAssetURL string) [] func NewServices() { InitDBConfig() newService() + newOAuth2Client() NewLogServices(false) newCacheService() newSessionService() From a3cf72bebaeecdf7bd4c36b169e0a8c5dc56456f Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Sat, 3 Apr 2021 09:24:04 +0000 Subject: [PATCH 22/27] Add automatic oauth2 scopes for github and google --- custom/conf/app.example.ini | 3 --- .../content/doc/advanced/config-cheat-sheet.en-us.md | 1 - modules/auth/oauth2/oauth2.go | 12 ++++++++++-- modules/setting/oauth2_client.go | 2 -- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 40c22a93a308b..5c707e73cb793 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -625,9 +625,6 @@ REGISTER_EMAIL_CONFIRM = ; Typical values are profile and email. ; For more information about the possible values see https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims OPENID_CONNECT_SCOPES = -; Scopes for the google oauth2 provider (seperated by space) -; Default scopes will provide only email, but for registration we may need more: email, profile -GOOGLE_SCOPES = ; Automatically create user accounts for new oauth2 users. ENABLE_AUTO_REGISTRATION = false ; The source of the username for new oauth2 accounts: diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index ab836f87e8060..5eb00ebe496c5 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -433,7 +433,6 @@ relation to port exhaustion. - `REGISTER_EMAIL_CONFIRM`: *[service]* **REGISTER\_EMAIL\_CONFIRM**: Set this to enable or disable email confirmation of OAuth2 auto-registration. (Overwrites the REGISTER\_EMAIL\_CONFIRM setting of the `[service]` section) - `OPENID_CONNECT_SCOPES`: **\**: List of additional openid connect scopes. (`openid` is implicitly added) -- `GOOGLE_SCOPES`: **\**: List of additional google scopes (we may need to write here `email profile`) - `ENABLE_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration for oauth2 authentication. - `USERNAME`: **nickname**: The source of the username for new oauth2 accounts: userid, nickname, email (username part). - `UPDATE_AVATAR`: **false**: Set this to update user avatar if available from the oauth2 provider. diff --git a/modules/auth/oauth2/oauth2.go b/modules/auth/oauth2/oauth2.go index b7da3f80c6e60..5d152e0a5588a 100644 --- a/modules/auth/oauth2/oauth2.go +++ b/modules/auth/oauth2/oauth2.go @@ -157,7 +157,11 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo emailURL = customURLMapping.EmailURL } } - provider = github.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, emailURL) + scopes := []string{} + if setting.OAuth2Client.EnableAutoRegistration { + scopes = append(scopes, "user:email") + } + provider = github.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, emailURL, scopes...) case "gitlab": authURL := gitlab.AuthURL tokenURL := gitlab.TokenURL @@ -175,7 +179,11 @@ func createProvider(providerName, providerType, clientID, clientSecret, openIDCo } provider = gitlab.NewCustomisedURL(clientID, clientSecret, callbackURL, authURL, tokenURL, profileURL, "read_user") case "gplus": // named gplus due to legacy gplus -> google migration (Google killed Google+). This ensures old connections still work - provider = google.New(clientID, clientSecret, callbackURL, setting.OAuth2Client.GoogleScopes...) + scopes := []string{"email"} + if setting.OAuth2Client.UpdateAvatar || setting.OAuth2Client.EnableAutoRegistration { + scopes = append(scopes, "profile") + } + provider = google.New(clientID, clientSecret, callbackURL, scopes...) case "openidConnect": if provider, err = openidConnect.New(clientID, clientSecret, callbackURL, openIDConnectAutoDiscoveryURL, setting.OAuth2Client.OpenIDConnectScopes...); err != nil { log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, openIDConnectAutoDiscoveryURL, err) diff --git a/modules/setting/oauth2_client.go b/modules/setting/oauth2_client.go index 56ad361bb39d4..a336563c9a456 100644 --- a/modules/setting/oauth2_client.go +++ b/modules/setting/oauth2_client.go @@ -54,7 +54,6 @@ func (accountLinking OAuth2AccountLinkingType) isValid() bool { var OAuth2Client struct { RegisterEmailConfirm bool OpenIDConnectScopes []string - GoogleScopes []string EnableAutoRegistration bool Username OAuth2UsernameType UpdateAvatar bool @@ -65,7 +64,6 @@ func newOAuth2Client() { sec := Cfg.Section("oauth2_client") OAuth2Client.RegisterEmailConfirm = sec.Key("REGISTER_EMAIL_CONFIRM").MustBool(Service.RegisterEmailConfirm) OAuth2Client.OpenIDConnectScopes = parseScopes(sec, "OPENID_CONNECT_SCOPES") - OAuth2Client.GoogleScopes = parseScopes(sec, "GOOGLE_SCOPES") OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameNickname))) if !OAuth2Client.Username.isValid() { From 3d99ee88d2585551844da086be3a2445db109fef Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Sat, 3 Apr 2021 09:33:22 +0000 Subject: [PATCH 23/27] Add hint to change the openid connect scopes if fields are missing --- routers/user/auth.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/routers/user/auth.go b/routers/user/auth.go index 2fdce58e4b10e..2034d921fe3a6 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -631,6 +631,9 @@ func SignInOAuthCallback(ctx *context.Context) { } if len(missingFields) > 0 { log.Error("OAuth2 Provider %s returned empty or missing fields: %s", loginSource.Name, missingFields) + if loginSource.IsOAuth2() && loginSource.OAuth2().Provider == "openidConnect" { + log.Error("You may need to change the 'OPENID_CONNECT_SCOPES' setting to request all required fields") + } err = fmt.Errorf("OAuth2 Provider %s returned empty or missing fields: %s", loginSource.Name, missingFields) ctx.ServerError("CreateUser", err) return From ebc0d0be05ffeb879168014e1a0d5a65846a39f2 Mon Sep 17 00:00:00 2001 From: Viktor Kuzmin Date: Sun, 4 Apr 2021 12:07:44 +0300 Subject: [PATCH 24/27] OAuth2 sign in is not handled properly after all merges --- routers/user/auth.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/routers/user/auth.go b/routers/user/auth.go index 994251169fba0..c9eb9edb70550 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -648,13 +648,14 @@ func SignInOAuthCallback(ctx *context.Context) { LoginName: gothUser.UserID, } - 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) { + return + } } else { // no existing user is found, request attach or new account showLinkingLogin(ctx, gothUser) + return } - - return } handleOAuth2SignIn(ctx, u, gothUser) From f320e342c316ea5c996a40795f2130e292c9dacd Mon Sep 17 00:00:00 2001 From: Viktor Kuzmin Date: Tue, 13 Apr 2021 18:50:54 +0300 Subject: [PATCH 25/27] More detailed description of options in cheat sheet --- .../doc/advanced/config-cheat-sheet.en-us.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 854d64b08828d..72cbd7e488942 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -433,10 +433,17 @@ relation to port exhaustion. - `REGISTER_EMAIL_CONFIRM`: *[service]* **REGISTER\_EMAIL\_CONFIRM**: Set this to enable or disable email confirmation of OAuth2 auto-registration. (Overwrites the REGISTER\_EMAIL\_CONFIRM setting of the `[service]` section) - `OPENID_CONNECT_SCOPES`: **\**: List of additional openid connect scopes. (`openid` is implicitly added) -- `ENABLE_AUTO_REGISTRATION`: **false**: Enable this to allow auto-registration for oauth2 authentication. -- `USERNAME`: **nickname**: The source of the username for new oauth2 accounts: userid, nickname, email (username part). -- `UPDATE_AVATAR`: **false**: Set this to update user avatar if available from the oauth2 provider. -- `ACCOUNT_LINKING`: **disabled**: How to handle if an account / email already exists: disabled / login / auto. +- `ENABLE_AUTO_REGISTRATION`: **false**: Automatically create user accounts for new oauth2 users. +- `USERNAME`: **nickname**: The source of the username for new oauth2 accounts: + - userid - use the userid / sub attribute + - nickname - use the nickname attribute + - email - use the username part of the email attribute +- `UPDATE_AVATAR`: **false**: Update avatar if available from oauth2 provider. Update will be performed on each login. +- `ACCOUNT_LINKING`: **disabled**: How to handle if an account / email already exists: + - disabled - show an error + - login - show an account linking login + - auto - link directly with the account, please be aware that it may automatically merge two accounts + only because they have same names. ## Service (`service`) From 29870812cd2ccda6bca9bef51baf9699308de513 Mon Sep 17 00:00:00 2001 From: Viktor Kuzmin Date: Tue, 13 Apr 2021 20:30:41 +0300 Subject: [PATCH 26/27] Correct info about auto linking security risk --- docs/content/doc/advanced/config-cheat-sheet.en-us.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 72cbd7e488942..0b19b77c2fd39 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -443,7 +443,7 @@ relation to port exhaustion. - disabled - show an error - login - show an account linking login - auto - link directly with the account, please be aware that it may automatically merge two accounts - only because they have same names. + only because they have same names/emails depending on the setting you choose to defariate nickname & identity from. ## Service (`service`) From 7b963963bab9f1ce815925cffd776b3c1df26f56 Mon Sep 17 00:00:00 2001 From: Martin Michaelis Date: Wed, 14 Apr 2021 11:52:49 +0200 Subject: [PATCH 27/27] Extend info about auto linking security risk --- docs/content/doc/advanced/config-cheat-sheet.en-us.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 0b19b77c2fd39..587563135ea6c 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -442,8 +442,7 @@ relation to port exhaustion. - `ACCOUNT_LINKING`: **disabled**: How to handle if an account / email already exists: - disabled - show an error - login - show an account linking login - - auto - link directly with the account, please be aware that it may automatically merge two accounts - only because they have same names/emails depending on the setting you choose to defariate nickname & identity from. + - auto - automatically link with the account (Please be aware that this will grant access to an existing account just because the same username or email is provided. You must make sure that this does not cause issues with your authentication providers.) ## Service (`service`)