From 2559c80bec27a41967b355d214253a83b9ee5dad Mon Sep 17 00:00:00 2001
From: Zettat123 <zettat123@gmail.com>
Date: Mon, 11 Mar 2024 14:07:36 +0800
Subject: [PATCH] Add a warning for disallowed email domains (#29658)

Resolve #29660

Follow #29522 and #29609

Add a warning for disallowed email domains when admins manually add/edit
users.

Thanks @yp05327 for the
[comment](https://github.com/go-gitea/gitea/pull/29605#issuecomment-1980105119)

![image](https://github.com/go-gitea/gitea/assets/15528715/6737b221-a3a2-4180-9ef8-b846c10f96e0)

(cherry picked from commit 4129e0e79bbf30e4297efd33feb2602c40322d10)
---
 models/user/email_address.go        | 18 +++++++++---------
 options/locale/locale_en-US.ini     |  1 +
 routers/api/v1/admin/user.go        |  9 +++++++++
 routers/web/admin/users.go          |  8 ++++++++
 services/forms/user_form.go         |  8 ++------
 tests/integration/api_admin_test.go |  6 ++++--
 6 files changed, 33 insertions(+), 17 deletions(-)

diff --git a/models/user/email_address.go b/models/user/email_address.go
index 1d90b127bf..f2ee5e61b2 100644
--- a/models/user/email_address.go
+++ b/models/user/email_address.go
@@ -548,17 +548,17 @@ func validateEmailBasic(email string) error {
 
 // validateEmailDomain checks whether the email domain is allowed or blocked
 func validateEmailDomain(email string) error {
-	// if there is no allow list, then check email against block list
-	if len(setting.Service.EmailDomainAllowList) == 0 &&
-		validation.IsEmailDomainListed(setting.Service.EmailDomainBlockList, email) {
-		return ErrEmailInvalid{email}
-	}
-
-	// if there is an allow list, then check email against allow list
-	if len(setting.Service.EmailDomainAllowList) > 0 &&
-		!validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email) {
+	if !IsEmailDomainAllowed(email) {
 		return ErrEmailInvalid{email}
 	}
 
 	return nil
 }
+
+func IsEmailDomainAllowed(email string) bool {
+	if len(setting.Service.EmailDomainAllowList) == 0 {
+		return !validation.IsEmailDomainListed(setting.Service.EmailDomainBlockList, email)
+	}
+
+	return validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, email)
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index e8b9f95208..f021fae153 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -586,6 +586,7 @@ team_name_been_taken = The team name is already taken.
 team_no_units_error = Allow access to at least one repository section.
 email_been_used = The email address is already used.
 email_invalid = The email address is invalid.
+email_domain_is_not_allowed = The domain of user email <b>%s</b> conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST. Please ensure your operation is expected.
 openid_been_used = The OpenID address "%s" is already used.
 username_password_incorrect = Username or password is incorrect.
 password_complexity = Password does not pass complexity requirements:
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index 986305d423..87a5b28fad 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -147,6 +147,11 @@ func CreateUser(ctx *context.APIContext) {
 		}
 		return
 	}
+
+	if !user_model.IsEmailDomainAllowed(u.Email) {
+		ctx.Resp.Header().Add("X-Gitea-Warning", fmt.Sprintf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", u.Email))
+	}
+
 	log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name)
 
 	// Send email notification.
@@ -220,6 +225,10 @@ func EditUser(ctx *context.APIContext) {
 			}
 			return
 		}
+
+		if !user_model.IsEmailDomainAllowed(*form.Email) {
+			ctx.Resp.Header().Add("X-Gitea-Warning", fmt.Sprintf("the domain of user email %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", *form.Email))
+		}
 	}
 
 	opts := &user_service.UpdateOptions{
diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go
index 671a0d8885..6dfcfc3d9a 100644
--- a/routers/web/admin/users.go
+++ b/routers/web/admin/users.go
@@ -202,6 +202,11 @@ func NewUserPost(ctx *context.Context) {
 		}
 		return
 	}
+
+	if !user_model.IsEmailDomainAllowed(u.Email) {
+		ctx.Flash.Warning(ctx.Tr("form.email_domain_is_not_allowed", u.Email))
+	}
+
 	log.Trace("Account created by admin (%s): %s", ctx.Doer.Name, u.Name)
 
 	// Send email notification.
@@ -425,6 +430,9 @@ func EditUserPost(ctx *context.Context) {
 			}
 			return
 		}
+		if !user_model.IsEmailDomainAllowed(form.Email) {
+			ctx.Flash.Warning(ctx.Tr("form.email_domain_is_not_allowed", form.Email))
+		}
 	}
 
 	opts := &user_service.UpdateOptions{
diff --git a/services/forms/user_form.go b/services/forms/user_form.go
index 77316fb13a..0f3cd0ceec 100644
--- a/services/forms/user_form.go
+++ b/services/forms/user_form.go
@@ -10,9 +10,9 @@ import (
 	"strings"
 
 	auth_model "code.gitea.io/gitea/models/auth"
+	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/validation"
 	"code.gitea.io/gitea/modules/web/middleware"
 	"code.gitea.io/gitea/services/context"
 
@@ -109,11 +109,7 @@ func (f *RegisterForm) Validate(req *http.Request, errs binding.Errors) binding.
 // domains in the whitelist or if it doesn't match any of
 // domains in the blocklist, if any such list is not empty.
 func (f *RegisterForm) IsEmailDomainAllowed() bool {
-	if len(setting.Service.EmailDomainAllowList) == 0 {
-		return !validation.IsEmailDomainListed(setting.Service.EmailDomainBlockList, f.Email)
-	}
-
-	return validation.IsEmailDomainListed(setting.Service.EmailDomainAllowList, f.Email)
+	return user_model.IsEmailDomainAllowed(f.Email)
 }
 
 // MustChangePasswordForm form for updating your password after account creation
diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go
index 8a330a68e2..e8954f5b20 100644
--- a/tests/integration/api_admin_test.go
+++ b/tests/integration/api_admin_test.go
@@ -354,7 +354,8 @@ func TestAPICreateUser_NotAllowedEmailDomain(t *testing.T) {
 		"password":             "allowedUser1_pass",
 		"must_change_password": "true",
 	}).AddTokenAuth(token)
-	MakeRequest(t, req, http.StatusCreated)
+	resp := MakeRequest(t, req, http.StatusCreated)
+	assert.Equal(t, "the domain of user email allowedUser1@example1.org conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", resp.Header().Get("X-Gitea-Warning"))
 
 	req = NewRequest(t, "DELETE", "/api/v1/admin/users/allowedUser1").AddTokenAuth(token)
 	MakeRequest(t, req, http.StatusNoContent)
@@ -378,7 +379,8 @@ func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) {
 		SourceID:  0,
 		Email:     &newEmail,
 	}).AddTokenAuth(token)
-	MakeRequest(t, req, http.StatusOK)
+	resp := MakeRequest(t, req, http.StatusOK)
+	assert.Equal(t, "the domain of user email user2@example1.com conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", resp.Header().Get("X-Gitea-Warning"))
 
 	originalEmail := "user2@example.com"
 	req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{