]> source.dussan.org Git - gitea.git/commitdiff
Make captcha and password optional for external accounts (#6606)
authorAJ ONeal <coolaj86@gmail.com>
Sat, 6 Jul 2019 19:48:02 +0000 (13:48 -0600)
committertechknowlogick <techknowlogick@gitea.io>
Sat, 6 Jul 2019 19:48:02 +0000 (15:48 -0400)
docs/content/doc/advanced/config-cheat-sheet.en-us.md
modules/auth/user_form.go
modules/setting/service.go
routers/user/auth.go
routers/user/auth_openid.go
templates/user/auth/signin_inner.tmpl
templates/user/auth/signup_inner.tmpl

index 12cad995e5b80366d5ec24fbd1aef962796e04f3..11dbfc5d095eca99ebe4891fc011093df981b778 100644 (file)
@@ -216,6 +216,9 @@ 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.
+- `REQUIRE_EXTERNAL_REGISTRATION_PASSWORD`: **false**: Enable this to force externally created
+   accounts (via GitHub, OpenID Connect, etc) to create a password. Warning: enabling this will
+   decrease security, so you should only enable it if you know what you're doing.
 - `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.
@@ -225,6 +228,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
 - `ENABLE_REVERSE_PROXY_EMAIL`: **false**: Enable this to allow to auto-registration with a
    provided email rather than a generated email.
 - `ENABLE_CAPTCHA`: **false**: Enable this to use captcha validation for registration.
+- `REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA`: **false**: Enable this to force captcha validation
+   even for External Accounts (i.e. GitHub, OpenID Connect, etc). You must `ENABLE_CAPTCHA` also.
 - `CAPTCHA_TYPE`: **image**: \[image, recaptcha\]
 - `RECAPTCHA_SECRET`: **""**: Go to https://www.google.com/recaptcha/admin to get a secret for recaptcha.
 - `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha.
@@ -419,7 +424,7 @@ NB: You must `REDIRECT_MACARON_LOG` and have `DISABLE_ROUTER_LOG` set to `false`
 
 ## Metrics (`metrics`)
 
-- `ENABLED`: **false**: Enables /metrics endpoint for prometheus. 
+- `ENABLED`: **false**: Enables /metrics endpoint for prometheus.
 - `TOKEN`: **\<empty\>**: You need to specify the token, if you want to include in the authorization the metrics . The same token need to be used in prometheus parameters `bearer_token` or `bearer_token_file`.
 
 ## API (`api`)
index 0c8bd30abc8d3ebbd72eb527db649352df3ef97f..c117d038be867a49585dd65f01e0f4a46f6dec71 100644 (file)
@@ -79,7 +79,7 @@ func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) bindin
 type RegisterForm struct {
        UserName           string `binding:"Required;AlphaDashDot;MaxSize(40)"`
        Email              string `binding:"Required;Email;MaxSize(254)"`
-       Password           string `binding:"Required;MaxSize(255)"`
+       Password           string `binding:"MaxSize(255)"`
        Retype             string
        GRecaptchaResponse string `form:"g-recaptcha-response"`
 }
@@ -129,6 +129,7 @@ func (f *MustChangePasswordForm) Validate(ctx *macaron.Context, errs binding.Err
 // SignInForm form for signing in with user/password
 type SignInForm struct {
        UserName string `binding:"Required;MaxSize(254)"`
+       // TODO remove required from password for SecondFactorAuthentication
        Password string `binding:"Required;MaxSize(255)"`
        Remember bool
 }
index 7e4fb8d7d9c964dd33e1d127527a20c365d7d5d4..97babc5aaf21528016435d534a9694252e6a09fb 100644 (file)
@@ -27,6 +27,8 @@ var Service struct {
        EnableReverseProxyAutoRegister          bool
        EnableReverseProxyEmail                 bool
        EnableCaptcha                           bool
+       RequireExternalRegistrationCaptcha      bool
+       RequireExternalRegistrationPassword     bool
        CaptchaType                             string
        RecaptchaSecret                         string
        RecaptchaSitekey                        string
@@ -61,6 +63,8 @@ func newService() {
        Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool()
        Service.EnableReverseProxyEmail = sec.Key("ENABLE_REVERSE_PROXY_EMAIL").MustBool()
        Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool(false)
+       Service.RequireExternalRegistrationCaptcha = sec.Key("REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA").MustBool(Service.EnableCaptcha)
+       Service.RequireExternalRegistrationPassword = sec.Key("REQUIRE_EXTERNAL_REGISTRATION_PASSWORD").MustBool()
        Service.CaptchaType = sec.Key("CAPTCHA_TYPE").MustString(ImageCaptcha)
        Service.RecaptchaSecret = sec.Key("RECAPTCHA_SECRET").MustString("")
        Service.RecaptchaSitekey = sec.Key("RECAPTCHA_SITEKEY").MustString("")
index 576f6305778baf8afcbef6007958236fe224c6f0..8203593739e8c7d4546ece4d28dae6a101607884 100644 (file)
@@ -697,9 +697,10 @@ func oAuth2UserLoginCallback(loginSource *models.LoginSource, request *http.Requ
 
 // LinkAccount shows the page where the user can decide to login or create a new account
 func LinkAccount(ctx *context.Context) {
+       ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationCaptcha || setting.Service.AllowOnlyExternalRegistration
        ctx.Data["Title"] = ctx.Tr("link_account")
        ctx.Data["LinkAccountMode"] = true
-       ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
+       ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha
        ctx.Data["CaptchaType"] = setting.Service.CaptchaType
        ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
        ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
@@ -746,10 +747,11 @@ func LinkAccount(ctx *context.Context) {
 
 // LinkAccountPostSignIn handle the coupling of external account with another account using signIn
 func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
+       ctx.Data["DisablePassword"] = setting.Service.AllowOnlyExternalRegistration
        ctx.Data["Title"] = ctx.Tr("link_account")
        ctx.Data["LinkAccountMode"] = true
        ctx.Data["LinkAccountModeSignIn"] = true
-       ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
+       ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha
        ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
        ctx.Data["CaptchaType"] = setting.Service.CaptchaType
        ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
@@ -824,10 +826,13 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
 
 // LinkAccountPostRegister handle the creation of a new account for an external account using signUp
 func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterForm) {
+       // TODO Make insecure passwords optional for local accounts also,
+       //      once email-based Second-Factor Auth is available
+       ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationCaptcha || setting.Service.AllowOnlyExternalRegistration
        ctx.Data["Title"] = ctx.Tr("link_account")
        ctx.Data["LinkAccountMode"] = true
        ctx.Data["LinkAccountModeRegister"] = true
-       ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha
+       ctx.Data["EnableCaptcha"] = setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha
        ctx.Data["RecaptchaURL"] = setting.Service.RecaptchaURL
        ctx.Data["CaptchaType"] = setting.Service.CaptchaType
        ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
@@ -854,14 +859,18 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
                return
        }
 
-       if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ImageCaptcha && !cpt.VerifyReq(ctx.Req) {
-               ctx.Data["Err_Captcha"] = true
-               ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplLinkAccount, &form)
-               return
-       }
+       if setting.Service.EnableCaptcha && setting.Service.RequireExternalRegistrationCaptcha {
+               var valid bool
+               switch setting.Service.CaptchaType {
+               case setting.ImageCaptcha:
+                       valid = cpt.VerifyReq(ctx.Req)
+               case setting.ReCaptcha:
+                       valid, _ = recaptcha.Verify(form.GRecaptchaResponse)
+               default:
+                       ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
+                       return
+               }
 
-       if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ReCaptcha {
-               valid, _ := recaptcha.Verify(form.GRecaptchaResponse)
                if !valid {
                        ctx.Data["Err_Captcha"] = true
                        ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplLinkAccount, &form)
@@ -869,15 +878,24 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
                }
        }
 
-       if (len(strings.TrimSpace(form.Password)) > 0 || len(strings.TrimSpace(form.Retype)) > 0) && form.Password != form.Retype {
-               ctx.Data["Err_Password"] = true
-               ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplLinkAccount, &form)
-               return
-       }
-       if len(strings.TrimSpace(form.Password)) > 0 && len(form.Password) < setting.MinPasswordLength {
-               ctx.Data["Err_Password"] = true
-               ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplLinkAccount, &form)
-               return
+       if setting.Service.AllowOnlyExternalRegistration || !setting.Service.RequireExternalRegistrationPassword {
+               // In models.User an empty password is classed as not set, so we set form.Password to empty.
+               // Eventually the database should be changed to indicate "Second Factor"-enabled accounts
+               // (accounts that do not introduce the security vulnerabilities of a password).
+               // If a user decides to circumvent second-factor security, and purposefully create a password,
+               // they can still do so using the "Recover Account" option.
+               form.Password = ""
+       } else {
+               if (len(strings.TrimSpace(form.Password)) > 0 || len(strings.TrimSpace(form.Retype)) > 0) && form.Password != form.Retype {
+                       ctx.Data["Err_Password"] = true
+                       ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplLinkAccount, &form)
+                       return
+               }
+               if len(strings.TrimSpace(form.Password)) > 0 && len(form.Password) < setting.MinPasswordLength {
+                       ctx.Data["Err_Password"] = true
+                       ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplLinkAccount, &form)
+                       return
+               }
        }
 
        loginSource, err := models.GetActiveOAuth2LoginSourceByName(gothUser.(goth.User).Provider)
@@ -1000,14 +1018,18 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo
                return
        }
 
-       if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ImageCaptcha && !cpt.VerifyReq(ctx.Req) {
-               ctx.Data["Err_Captcha"] = true
-               ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUp, &form)
-               return
-       }
+       if setting.Service.EnableCaptcha {
+               var valid bool
+               switch setting.Service.CaptchaType {
+               case setting.ImageCaptcha:
+                       valid = cpt.VerifyReq(ctx.Req)
+               case setting.ReCaptcha:
+                       valid, _ = recaptcha.Verify(form.GRecaptchaResponse)
+               default:
+                       ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
+                       return
+               }
 
-       if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ReCaptcha {
-               valid, _ := recaptcha.Verify(form.GRecaptchaResponse)
                if !valid {
                        ctx.Data["Err_Captcha"] = true
                        ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUp, &form)
index f98c07acd79f8df91d6d476b92f5b2ec86cdba31..d6baf0d92b8e7b1a7ee951906e0d31a99c96d49e 100644 (file)
@@ -357,19 +357,23 @@ func RegisterOpenIDPost(ctx *context.Context, cpt *captcha.Captcha, form auth.Si
        ctx.Data["RecaptchaSitekey"] = setting.Service.RecaptchaSitekey
        ctx.Data["OpenID"] = oid
 
-       if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ImageCaptcha && !cpt.VerifyReq(ctx.Req) {
-               ctx.Data["Err_Captcha"] = true
-               ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUpOID, &form)
-               return
-       }
-
-       if setting.Service.EnableCaptcha && setting.Service.CaptchaType == setting.ReCaptcha {
-               err := ctx.Req.ParseForm()
-               if err != nil {
-                       ctx.ServerError("", err)
+       if setting.Service.EnableCaptcha {
+               var valid bool
+               switch setting.Service.CaptchaType {
+               case setting.ImageCaptcha:
+                       valid = cpt.VerifyReq(ctx.Req)
+               case setting.ReCaptcha:
+                       err := ctx.Req.ParseForm()
+                       if err != nil {
+                               ctx.ServerError("", err)
+                               return
+                       }
+                       valid, _ = recaptcha.Verify(form.GRecaptchaResponse)
+               default:
+                       ctx.ServerError("Unknown Captcha Type", fmt.Errorf("Unknown Captcha Type: %s", setting.Service.CaptchaType))
                        return
                }
-               valid, _ := recaptcha.Verify(form.GRecaptchaResponse)
+
                if !valid {
                        ctx.Data["Err_Captcha"] = true
                        ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tplSignUpOID, &form)
index 3e67aa7b321399e41ef8bb16981712cc15c7cf15..07f85c954f614397160ac92abacc150287f17c61 100644 (file)
                                <label for="user_name">{{.i18n.Tr "home.uname_holder"}}</label>
                                <input id="user_name" name="user_name" value="{{.user_name}}" autofocus required>
                        </div>
+                       {{if not .DisablePassword}}
                        <div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
                                <label for="password">{{.i18n.Tr "password"}}</label>
                                <input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" required>
                        </div>
+                       {{end}}
                        {{if not .LinkAccountMode}}
                        <div class="inline field">
                                <label></label>
index 25b50dad8669d3b8827d2244d17792d925c4ffc8..cdacd910d9b2ac30b37fb682e8cb49cc849a5978 100644 (file)
                                                        <label for="email">{{.i18n.Tr "email"}}</label>
                                                        <input id="email" name="email" type="email" value="{{.email}}" required>
                                                </div>
-                                               <div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
-                                                       <label for="password">{{.i18n.Tr "password"}}</label>
-                                                       <input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" required>
-                                               </div>
-                                               <div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
-                                                       <label for="retype">{{.i18n.Tr "re_type"}}</label>
-                                                       <input id="retype" name="retype" type="password" value="{{.retype}}" autocomplete="off" required>
-                                               </div>
+
+                                               {{if not .DisablePassword}}
+                                                       <div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
+                                                               <label for="password">{{.i18n.Tr "password"}}</label>
+                                                               <input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" required>
+                                                       </div>
+                                                       <div class="required inline field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
+                                                               <label for="retype">{{.i18n.Tr "re_type"}}</label>
+                                                               <input id="retype" name="retype" type="password" value="{{.retype}}" autocomplete="off" required>
+                                                       </div>
+                                               {{end}}
                                                {{if and .EnableCaptcha (eq .CaptchaType "image")}}
                                                        <div class="inline field">
                                                                <label></label>