mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 11:35:03 +01:00 
			
		
		
		
	Allow to disable the password-based login (sign-in) form (#32687)
Usually enterprise/organization users would like to only allow OAuth2 login. This PR adds a new config option to disable the password-based login form. It is a simple and clear approach and won't block the future login-system refactoring works. Fix a TODO in #24821 Replace #21851 Close #7633 , close #13606
This commit is contained in:
		
							parent
							
								
									1bb1a51f47
								
							
						
					
					
						commit
						def13ece7c
					
				| @ -784,6 +784,10 @@ LEVEL = Info | ||||
| ;; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token | ||||
| ;ENABLE_BASIC_AUTHENTICATION = true | ||||
| ;; | ||||
| ;; Show the password sign-in form (for password-based login), otherwise, only show OAuth2 login methods. | ||||
| ;; If you set it to false, maybe it also needs to set ENABLE_BASIC_AUTHENTICATION to false to completely disable password-based authentication. | ||||
| ;ENABLE_PASSWORD_SIGNIN_FORM = true | ||||
| ;; | ||||
| ;; More detail: https://github.com/gogits/gogs/issues/165 | ||||
| ;ENABLE_REVERSE_PROXY_AUTHENTICATION = false | ||||
| ; Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible. | ||||
| @ -1944,13 +1948,13 @@ LEVEL = Info | ||||
| ;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` | ||||
| ;MINIO_SECRET_ACCESS_KEY = | ||||
| ;; | ||||
| ;; Preferred IAM Endpoint to override Minio's default IAM Endpoint resolution only available when STORAGE_TYPE is `minio`.  | ||||
| ;; If not provided and STORAGE_TYPE is `minio`, will search for and derive endpoint from known environment variables  | ||||
| ;; (AWS_CONTAINER_AUTHORIZATION_TOKEN, AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,  | ||||
| ;; AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME, AWS_REGION),  | ||||
| ;; Preferred IAM Endpoint to override Minio's default IAM Endpoint resolution only available when STORAGE_TYPE is `minio`. | ||||
| ;; If not provided and STORAGE_TYPE is `minio`, will search for and derive endpoint from known environment variables | ||||
| ;; (AWS_CONTAINER_AUTHORIZATION_TOKEN, AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI, | ||||
| ;; AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME, AWS_REGION), | ||||
| ;; or the DefaultIAMRoleEndpoint if not provided otherwise. | ||||
| ;MINIO_IAM_ENDPOINT = | ||||
| ;;  | ||||
| ;; | ||||
| ;; Minio bucket to store the attachments only available when STORAGE_TYPE is `minio` | ||||
| ;MINIO_BUCKET = gitea | ||||
| ;; | ||||
| @ -2695,10 +2699,10 @@ LEVEL = Info | ||||
| ;; Minio secretAccessKey to connect only available when STORAGE_TYPE is `minio` | ||||
| ;MINIO_SECRET_ACCESS_KEY = | ||||
| ;; | ||||
| ;; Preferred IAM Endpoint to override Minio's default IAM Endpoint resolution only available when STORAGE_TYPE is `minio`.  | ||||
| ;; If not provided and STORAGE_TYPE is `minio`, will search for and derive endpoint from known environment variables  | ||||
| ;; (AWS_CONTAINER_AUTHORIZATION_TOKEN, AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,  | ||||
| ;; AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME, AWS_REGION),  | ||||
| ;; Preferred IAM Endpoint to override Minio's default IAM Endpoint resolution only available when STORAGE_TYPE is `minio`. | ||||
| ;; If not provided and STORAGE_TYPE is `minio`, will search for and derive endpoint from known environment variables | ||||
| ;; (AWS_CONTAINER_AUTHORIZATION_TOKEN, AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE, AWS_CONTAINER_CREDENTIALS_RELATIVE_URI, | ||||
| ;; AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME, AWS_REGION), | ||||
| ;; or the DefaultIAMRoleEndpoint if not provided otherwise. | ||||
| ;MINIO_IAM_ENDPOINT = | ||||
| ;; | ||||
|  | ||||
| @ -41,6 +41,7 @@ var Service = struct { | ||||
| 	AllowOnlyInternalRegistration           bool | ||||
| 	AllowOnlyExternalRegistration           bool | ||||
| 	ShowRegistrationButton                  bool | ||||
| 	EnablePasswordSignInForm                bool | ||||
| 	ShowMilestonesDashboardPage             bool | ||||
| 	RequireSignInView                       bool | ||||
| 	EnableNotifyMail                        bool | ||||
| @ -159,6 +160,7 @@ func loadServiceFrom(rootCfg ConfigProvider) { | ||||
| 	Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true) | ||||
| 	Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool() | ||||
| 	Service.EnableBasicAuth = sec.Key("ENABLE_BASIC_AUTHENTICATION").MustBool(true) | ||||
| 	Service.EnablePasswordSignInForm = sec.Key("ENABLE_PASSWORD_SIGNIN_FORM").MustBool(true) | ||||
| 	Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool() | ||||
| 	Service.EnableReverseProxyAuthAPI = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION_API").MustBool() | ||||
| 	Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool() | ||||
|  | ||||
| @ -160,54 +160,42 @@ func CheckAutoLogin(ctx *context.Context) bool { | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // SignIn render sign in page | ||||
| func SignIn(ctx *context.Context) { | ||||
| func prepareSignInPageData(ctx *context.Context) { | ||||
| 	ctx.Data["Title"] = ctx.Tr("sign_in") | ||||
| 
 | ||||
| 	if CheckAutoLogin(ctx) { | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if ctx.IsSigned { | ||||
| 		RedirectAfterLogin(ctx) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true)) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("UserSignIn", err) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Data["OAuth2Providers"] = oauth2Providers | ||||
| 	ctx.Data["OAuth2Providers"], _ = oauth2.GetOAuth2Providers(ctx, optional.Some(true)) | ||||
| 	ctx.Data["Title"] = ctx.Tr("sign_in") | ||||
| 	ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" | ||||
| 	ctx.Data["PageIsSignIn"] = true | ||||
| 	ctx.Data["PageIsLogin"] = true | ||||
| 	ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx) | ||||
| 	ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm | ||||
| 
 | ||||
| 	if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin { | ||||
| 		context.SetCaptchaData(ctx) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // SignIn render sign in page | ||||
| func SignIn(ctx *context.Context) { | ||||
| 	if CheckAutoLogin(ctx) { | ||||
| 		return | ||||
| 	} | ||||
| 	if ctx.IsSigned { | ||||
| 		RedirectAfterLogin(ctx) | ||||
| 		return | ||||
| 	} | ||||
| 	prepareSignInPageData(ctx) | ||||
| 	ctx.HTML(http.StatusOK, tplSignIn) | ||||
| } | ||||
| 
 | ||||
| // SignInPost response for sign in request | ||||
| func SignInPost(ctx *context.Context) { | ||||
| 	ctx.Data["Title"] = ctx.Tr("sign_in") | ||||
| 
 | ||||
| 	oauth2Providers, err := oauth2.GetOAuth2Providers(ctx, optional.Some(true)) | ||||
| 	if err != nil { | ||||
| 		ctx.ServerError("UserSignIn", err) | ||||
| 	if !setting.Service.EnablePasswordSignInForm { | ||||
| 		ctx.Error(http.StatusForbidden) | ||||
| 		return | ||||
| 	} | ||||
| 	ctx.Data["OAuth2Providers"] = oauth2Providers | ||||
| 	ctx.Data["Title"] = ctx.Tr("sign_in") | ||||
| 	ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" | ||||
| 	ctx.Data["PageIsSignIn"] = true | ||||
| 	ctx.Data["PageIsLogin"] = true | ||||
| 	ctx.Data["EnableSSPI"] = auth.IsSSPIEnabled(ctx) | ||||
| 
 | ||||
| 	prepareSignInPageData(ctx) | ||||
| 	if ctx.HasError() { | ||||
| 		ctx.HTML(http.StatusOK, tplSignIn) | ||||
| 		return | ||||
| @ -216,8 +204,6 @@ func SignInPost(ctx *context.Context) { | ||||
| 	form := web.GetForm(ctx).(*forms.SignInForm) | ||||
| 
 | ||||
| 	if setting.Service.EnableCaptcha && setting.Service.RequireCaptchaForLogin { | ||||
| 		context.SetCaptchaData(ctx) | ||||
| 
 | ||||
| 		context.VerifyCaptcha(ctx, tplSignIn, form) | ||||
| 		if ctx.Written() { | ||||
| 			return | ||||
|  | ||||
| @ -1,7 +1,3 @@ | ||||
| {{if or .OAuth2Providers .EnableOpenIDSignIn}} | ||||
| <div class="divider divider-text"> | ||||
| 	{{ctx.Locale.Tr "sign_in_or"}} | ||||
| </div> | ||||
| <div id="oauth2-login-navigator" class="tw-py-1"> | ||||
| 	<div class="tw-flex tw-flex-col tw-justify-center"> | ||||
| 		<div id="oauth2-login-navigator-inner" class="tw-flex tw-flex-col tw-flex-wrap tw-items-center tw-gap-2"> | ||||
| @ -26,4 +22,3 @@ | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| {{end}} | ||||
|  | ||||
| @ -10,6 +10,7 @@ | ||||
| 		{{end}} | ||||
| 	</h4> | ||||
| 	<div class="ui attached segment"> | ||||
| 		{{if .EnablePasswordSignInForm}} | ||||
| 		<form class="ui form" action="{{.SignInLink}}" method="post"> | ||||
| 			{{.CsrfTokenHtml}} | ||||
| 			<div class="required field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}"> | ||||
| @ -46,8 +47,13 @@ | ||||
| 				</button> | ||||
| 			</div> | ||||
| 		</form> | ||||
| 
 | ||||
| 		{{template "user/auth/oauth_container" .}} | ||||
| 		{{end}}{{/*if .EnablePasswordSignInForm*/}} | ||||
| 		{{if and .OAuth2Providers .EnableOpenIDSignIn .EnablePasswordSignInForm}} | ||||
| 			<div class="divider divider-text">{{ctx.Locale.Tr "sign_in_or"}}</div> | ||||
| 		{{end}} | ||||
| 		{{if and .OAuth2Providers .EnableOpenIDSignIn}} | ||||
| 			{{template "user/auth/oauth_container" .}} | ||||
| 		{{end}} | ||||
| 	</div> | ||||
| </div> | ||||
| 
 | ||||
|  | ||||
| @ -48,7 +48,10 @@ | ||||
| 				</div> | ||||
| 			{{end}} | ||||
| 
 | ||||
| 			{{template "user/auth/oauth_container" .}} | ||||
| 			{{if and .OAuth2Providers .EnableOpenIDSignIn}} | ||||
| 				<div class="divider divider-text">{{ctx.Locale.Tr "sign_in_or"}}</div> | ||||
| 				{{template "user/auth/oauth_container" .}} | ||||
| 			{{end}} | ||||
| 		</form> | ||||
| 	</div> | ||||
| </div> | ||||
|  | ||||
| @ -12,6 +12,7 @@ import ( | ||||
| 	"code.gitea.io/gitea/models/unittest" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/test" | ||||
| 	"code.gitea.io/gitea/modules/translation" | ||||
| 	"code.gitea.io/gitea/tests" | ||||
| 
 | ||||
| @ -91,3 +92,31 @@ func TestSigninWithRememberMe(t *testing.T) { | ||||
| 	req = NewRequest(t, "GET", "/user/settings") | ||||
| 	session.MakeRequest(t, req, http.StatusOK) | ||||
| } | ||||
| 
 | ||||
| func TestEnablePasswordSignInForm(t *testing.T) { | ||||
| 	defer tests.PrepareTestEnv(t)() | ||||
| 
 | ||||
| 	t.Run("EnablePasswordSignInForm=false", func(t *testing.T) { | ||||
| 		defer tests.PrintCurrentTest(t)() | ||||
| 		defer test.MockVariableValue(&setting.Service.EnablePasswordSignInForm, false)() | ||||
| 
 | ||||
| 		req := NewRequest(t, "GET", "/user/login") | ||||
| 		resp := MakeRequest(t, req, http.StatusOK) | ||||
| 		NewHTMLParser(t, resp.Body).AssertElement(t, "form[action='/user/login']", false) | ||||
| 
 | ||||
| 		req = NewRequest(t, "POST", "/user/login") | ||||
| 		MakeRequest(t, req, http.StatusForbidden) | ||||
| 	}) | ||||
| 
 | ||||
| 	t.Run("EnablePasswordSignInForm=true", func(t *testing.T) { | ||||
| 		defer tests.PrintCurrentTest(t)() | ||||
| 		defer test.MockVariableValue(&setting.Service.EnablePasswordSignInForm, true)() | ||||
| 
 | ||||
| 		req := NewRequest(t, "GET", "/user/login") | ||||
| 		resp := MakeRequest(t, req, http.StatusOK) | ||||
| 		NewHTMLParser(t, resp.Body).AssertElement(t, "form[action='/user/login']", true) | ||||
| 
 | ||||
| 		req = NewRequest(t, "POST", "/user/login") | ||||
| 		MakeRequest(t, req, http.StatusOK) | ||||
| 	}) | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user