From b97af15de67b04fd259bd70a4abbc873f12e9491 Mon Sep 17 00:00:00 2001
From: Lanre Adelowo <adelowomailbox@gmail.com>
Date: Thu, 15 Nov 2018 02:00:04 +0100
Subject: [PATCH] Block registration based on email domain (#5157)

* implement email domain whitelist
---
 custom/conf/app.ini.sample                    |  3 +
 .../doc/advanced/config-cheat-sheet.en-us.md  |  2 +
 modules/auth/user_form.go                     | 29 +++++++++
 modules/auth/user_form_test.go                | 64 +++++++++++++++++++
 modules/setting/setting.go                    |  2 +
 options/locale/locale_en-US.ini               |  1 +
 routers/user/auth.go                          |  5 ++
 7 files changed, 106 insertions(+)
 create mode 100644 modules/auth/user_form_test.go

diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample
index f42fa122b8..92d72e8c9b 100644
--- a/custom/conf/app.ini.sample
+++ b/custom/conf/app.ini.sample
@@ -311,6 +311,9 @@ ACTIVE_CODE_LIVE_MINUTES = 180
 RESET_PASSWD_CODE_LIVE_MINUTES = 180
 ; Whether a new user needs to confirm their email when registering.
 REGISTER_EMAIL_CONFIRM = false
+; List of domain names that are allowed to be used to register on a Gitea instance
+; gitea.io,example.com
+EMAIL_DOMAIN_WHITELIST=
 ; Disallow registration, only allow admins to create accounts.
 DISABLE_REGISTRATION = false
 ; Allow registration only using third part services, it works only when DISABLE_REGISTRATION is 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 b0ab78d2f1..a3bded679d 100644
--- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md
+++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md
@@ -194,6 +194,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
 - `RECAPTCHA_SITEKEY`: **""**: Go to https://www.google.com/recaptcha/admin to get a sitekey for recaptcha.
 - `DEFAULT_ENABLE_DEPENDENCIES`: **true** Enable this to have dependencies enabled by default.
 - `ENABLE_USER_HEATMAP`: **true** Enable this to display the heatmap on users profiles.
+- `EMAIL_DOMAIN_WHITELIST`: **\<empty\>**: If non-empty, list of domain names that can only be used to register
+  on this instance.
 
 ## Webhook (`webhook`)
 
diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go
index 43ddb29c76..c281672fe1 100644
--- a/modules/auth/user_form.go
+++ b/modules/auth/user_form.go
@@ -1,4 +1,5 @@
 // Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2018 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.
 
@@ -6,6 +7,9 @@ package auth
 
 import (
 	"mime/multipart"
+	"strings"
+
+	"code.gitea.io/gitea/modules/setting"
 
 	"github.com/go-macaron/binding"
 	"gopkg.in/macaron.v1"
@@ -84,6 +88,31 @@ func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) bindi
 	return validate(errs, ctx.Data, f, ctx.Locale)
 }
 
+// IsEmailDomainWhitelisted validates that the email address
+// provided by the user matches what has been configured .
+// If the domain whitelist from the config is empty, it marks the
+// email as whitelisted
+func (f RegisterForm) IsEmailDomainWhitelisted() bool {
+	if len(setting.Service.EmailDomainWhitelist) == 0 {
+		return true
+	}
+
+	n := strings.LastIndex(f.Email, "@")
+	if n <= 0 {
+		return false
+	}
+
+	domain := strings.ToLower(f.Email[n+1:])
+
+	for _, v := range setting.Service.EmailDomainWhitelist {
+		if strings.ToLower(v) == domain {
+			return true
+		}
+	}
+
+	return false
+}
+
 // MustChangePasswordForm form for updating your password after account creation
 // by an admin
 type MustChangePasswordForm struct {
diff --git a/modules/auth/user_form_test.go b/modules/auth/user_form_test.go
new file mode 100644
index 0000000000..084174622e
--- /dev/null
+++ b/modules/auth/user_form_test.go
@@ -0,0 +1,64 @@
+// Copyright 2018 The Gogs 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 auth
+
+import (
+	"testing"
+
+	"code.gitea.io/gitea/modules/setting"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestRegisterForm_IsDomainWhiteList_Empty(t *testing.T) {
+	_ = setting.Service
+
+	setting.Service.EmailDomainWhitelist = []string{}
+
+	form := RegisterForm{}
+
+	assert.True(t, form.IsEmailDomainWhitelisted())
+}
+
+func TestRegisterForm_IsDomainWhiteList_InvalidEmail(t *testing.T) {
+	_ = setting.Service
+
+	setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
+
+	tt := []struct {
+		email string
+	}{
+		{"securitygieqqq"},
+		{"hdudhdd"},
+	}
+
+	for _, v := range tt {
+		form := RegisterForm{Email: v.email}
+
+		assert.False(t, form.IsEmailDomainWhitelisted())
+	}
+}
+
+func TestRegisterForm_IsDomainWhiteList_ValidEmail(t *testing.T) {
+	_ = setting.Service
+
+	setting.Service.EmailDomainWhitelist = []string{"gitea.io"}
+
+	tt := []struct {
+		email string
+		valid bool
+	}{
+		{"security@gitea.io", true},
+		{"security@gITea.io", true},
+		{"hdudhdd", false},
+		{"seee@example.com", false},
+	}
+
+	for _, v := range tt {
+		form := RegisterForm{Email: v.email}
+
+		assert.Equal(t, v.valid, form.IsEmailDomainWhitelisted())
+	}
+}
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index b31162c140..b0bcd2ead8 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -1215,6 +1215,7 @@ var Service struct {
 	ActiveCodeLives                         int
 	ResetPwdCodeLives                       int
 	RegisterEmailConfirm                    bool
+	EmailDomainWhitelist                    []string
 	DisableRegistration                     bool
 	AllowOnlyExternalRegistration           bool
 	ShowRegistrationButton                  bool
@@ -1248,6 +1249,7 @@ func newService() {
 	Service.ResetPwdCodeLives = sec.Key("RESET_PASSWD_CODE_LIVE_MINUTES").MustInt(180)
 	Service.DisableRegistration = sec.Key("DISABLE_REGISTRATION").MustBool()
 	Service.AllowOnlyExternalRegistration = sec.Key("ALLOW_ONLY_EXTERNAL_REGISTRATION").MustBool()
+	Service.EmailDomainWhitelist = sec.Key("EMAIL_DOMAIN_WHITELIST").Strings(",")
 	Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration))
 	Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
 	Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool()
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index a82acc3dd1..f806d631ea 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -242,6 +242,7 @@ openid_register_title = Create new account
 openid_register_desc = The chosen OpenID URI is unknown. Associate it with a new account here.
 openid_signin_desc = Enter your OpenID URI. For example: https://anne.me, bob.openid.org.cn or gnusocial.net/carry.
 disable_forgot_password_mail = Password reset is disabled. Please contact your site administrator.
+email_domain_blacklisted = You cannot register with your email address.
 
 [mail]
 activate_account = Please activate your account
diff --git a/routers/user/auth.go b/routers/user/auth.go
index 25aa437efd..24b35e6f62 100644
--- a/routers/user/auth.go
+++ b/routers/user/auth.go
@@ -948,6 +948,11 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo
 		}
 	}
 
+	if !form.IsEmailDomainWhitelisted() {
+		ctx.RenderWithErr(ctx.Tr("auth.email_domain_blacklisted"), tplSignUp, &form)
+		return
+	}
+
 	if form.Password != form.Retype {
 		ctx.Data["Err_Password"] = true
 		ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplSignUp, &form)