FEATURE: add setting `auto_approve_email_domains` to auto approve users (#9323)

FEATURE: add setting auto_approve_email_domains to auto approve users (#9323)

  • FEATURE: add setting auto_approve_email_domains to auto approve users

This commit adds a new site setting auto_approve_email_domains to auto approve users based on their email address domain.

Note that if a domain already exists in email_domains_whitelist then auto_approve_email_domains needs to be duplicated there as well, since users won’t be able to register with email address that is not allowed in email_domains_whitelist.

  • Update config/locales/server.en.yml

Co-Authored-By: Robin Ward robin.ward@gmail.com

diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 79bee42..15e36dd 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -447,8 +447,11 @@ class UsersController < ApplicationController
     user ||= User.new
     user.attributes = new_user_params
 
-    # Handle API approval
-    ReviewableUser.set_approved_fields!(user, current_user) if user.approved?
+    # Handle API approval and
+    # auto approve users based on auto_approve_email_domains setting
+    if user.approved? || EmailValidator.can_auto_approve_user?(user.email)
+      ReviewableUser.set_approved_fields!(user, current_user)
+    end
 
     # Handle custom fields
     user_fields = UserField.all
diff --git a/app/models/invite_redeemer.rb b/app/models/invite_redeemer.rb
index 68a415d..172e06f 100644
--- a/app/models/invite_redeemer.rb
+++ b/app/models/invite_redeemer.rb
@@ -36,7 +36,9 @@ InviteRedeemer = Struct.new(:invite, :username, :name, :password, :user_custom_f
       registration_ip_address: ip_address
     }
 
-    if !SiteSetting.must_approve_users? || (SiteSetting.must_approve_users? && invite.invited_by.staff?)
+    if !SiteSetting.must_approve_users? ||
+        (SiteSetting.must_approve_users? && invite.invited_by.staff?) ||
+        EmailValidator.can_auto_approve_user?(user.email)
       ReviewableUser.set_approved_fields!(user, invite.invited_by)
     end
 
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index b1fd878..8fa0a36 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1582,6 +1582,7 @@ en:
     allow_index_in_robots_txt: "Specify in robots.txt that this site is allowed to be indexed by web search engines. In exceptional cases you can permanently <a href='%{base_path}/admin/customize/robots'>override robots.txt</a>."
     email_domains_blacklist: "A pipe-delimited list of email domains that users are not allowed to register accounts with. Example: mailinator.com|trashmail.net"
     email_domains_whitelist: "A pipe-delimited list of email domains that users MUST register accounts with. WARNING: Users with email domains other than those listed will not be allowed!"
+    auto_approve_email_domains: "Users with email addresses from this list of domains will be automatically approved."
     hide_email_address_taken: "Don't inform users that an account exists with a given email address during signup and from the forgot password form."
     log_out_strict: "When logging out, log out ALL sessions for the user on all devices"
     version_checks: "Ping the Discourse Hub for version updates and show new version messages on the <a href='%{base_path}/admin' target='_blank'>/admin</a> dashboard"
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 52b7c60..33501a4 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -459,6 +459,9 @@ login:
   email_domains_whitelist:
     default: ""
     type: list
+  auto_approve_email_domains:
+    default: ""
+    type: list
   hide_email_address_taken: false
   log_out_strict: true
   pending_users_reminder_delay:
diff --git a/lib/validators/email_validator.rb b/lib/validators/email_validator.rb
index 46d47f8..6436161 100644
--- a/lib/validators/email_validator.rb
+++ b/lib/validators/email_validator.rb
@@ -22,6 +22,14 @@ class EmailValidator < ActiveModel::EachValidator
     true
   end
 
+  def self.can_auto_approve_user?(email)
+    if (setting = SiteSetting.auto_approve_email_domains).present?
+      return !!(EmailValidator.allowed?(email) && email_in_restriction_setting?(setting, email))
+    end
+
+    false
+  end
+
   def self.email_in_restriction_setting?(setting, value)
     domains = setting.gsub('.', '\.')
     regexp = Regexp.new("@(.+\\.)?(#{domains})$", true)
diff --git a/spec/components/validators/email_validator_spec.rb b/spec/components/validators/email_validator_spec.rb
index 0c46168..41c29e1 100644
--- a/spec/components/validators/email_validator_spec.rb
+++ b/spec/components/validators/email_validator_spec.rb
@@ -41,6 +41,23 @@ describe EmailValidator do
     end
   end
 
+  context "auto approve email domains" do
+    it "works as expected" do
+      SiteSetting.auto_approve_email_domains = "example.com"
+
+      expect(EmailValidator.can_auto_approve_user?("foobar@example.com.fr")).to eq(false)
+      expect(EmailValidator.can_auto_approve_user?("foobar@example.com")).to eq(true)
+    end
+
+    it "returns false if domain not present in email_domains_whitelist" do
+      SiteSetting.email_domains_whitelist = "googlemail.com"
+      SiteSetting.auto_approve_email_domains = "example.com|googlemail.com"
+
+      expect(EmailValidator.can_auto_approve_user?("foobar@example.com")).to eq(false)
+      expect(EmailValidator.can_auto_approve_user?("foobar@googlemail.com")).to eq(true)
+    end
+  end
+
   context '.email_regex' do
     it 'should match valid emails' do
       expect(!!('test@discourse.org' =~ EmailValidator.email_regex)).to eq(true)
diff --git a/spec/models/invite_redeemer_spec.rb b/spec/models/invite_redeemer_spec.rb
index 6098add..814fe42 100644
--- a/spec/models/invite_redeemer_spec.rb
+++ b/spec/models/invite_redeemer_spec.rb
@@ -59,7 +59,7 @@ describe InviteRedeemer do
   end
 
   describe "#redeem" do
-    fab!(:invite) { Fabricate(:invite) }
+    fab!(:invite) { Fabricate(:invite, email: "foobar@example.com") }
     let(:name) { 'john snow' }
     let(:username) { 'kingofthenorth' }
     let(:password) { 'know5nOthiNG' }
@@ -102,6 +102,16 @@ describe InviteRedeemer do
       expect(user.approved).to eq(true)
     end
 
+    it "should redeem the invite if invited by non staff and approve if email in auto_approve_email_domains setting" do
+      SiteSetting.must_approve_users = true
+      SiteSetting.auto_approve_email_domains = "example.com"
+      user = invite_redeemer.redeem
+
+      expect(user.name).to eq(name)
+      expect(user.username).to eq(username)
+      expect(user.approved).to eq(true)
+    end
+
     it "should not blow up if invited_by user has been removed" do
       invite.invited_by.destroy!
       invite.reload
diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb
index 95d675f..561eaeb 100644
--- a/spec/requests/users_controller_spec.rb
+++ b/spec/requests/users_controller_spec.rb
@@ -583,7 +583,7 @@ describe UsersController do
       UsersController.any_instance.stubs(:honeypot_value).returns(nil)
       UsersController.any_instance.stubs(:challenge_value).returns(nil)
       SiteSetting.allow_new_registrations = true
-      @user = Fabricate.build(:user, password: "strongpassword")
+      @user = Fabricate.build(:user, email: "foobar@example.com", password: "strongpassword")
     end
 
     let(:post_user_params) do
@@ -826,6 +826,22 @@ describe UsersController do
           new_user = User.find(json["user_id"])
           expect(new_user.locale).not_to eq("fr")
         end
+
+        it "will auto approve user if the user email domain matches auto_approve_email_domains setting" do
+          Jobs.run_immediately!
+          SiteSetting.must_approve_users = true
+          SiteSetting.auto_approve_email_domains = "example.com"
+
+          post "/u.json", params: post_user_params.merge(active: true, api_key: api_key.key)
+
+          expect(response.status).to eq(200)
+          json = JSON.parse(response.body)
+
+          new_user = User.find(json["user_id"])
+          expect(json['active']).to be_truthy
+          expect(new_user.approved).to be_truthy
+          expect(ReviewableUser.pending.find_by(target: new_user)).to be_blank
+        end
       end
     end
 

GitHub sha: b2a0d34b

This commit appears in #9323 which was approved by eviltrout. It was merged by techAPJ.