Commit 5080178b authored by nicolasdular's avatar nicolasdular

Add restrictions for email addresses

Additionally to blacklisted domains, this adds the possibility
to forbid the whole email address based on a regex.
parent c9003a71
...@@ -315,7 +315,9 @@ module ApplicationSettingsHelper ...@@ -315,7 +315,9 @@ module ApplicationSettingsHelper
:push_event_hooks_limit, :push_event_hooks_limit,
:push_event_activities_limit, :push_event_activities_limit,
:custom_http_clone_url_root, :custom_http_clone_url_root,
:snippet_size_limit :snippet_size_limit,
:email_restrictions_enabled,
:email_restrictions
] ]
end end
......
...@@ -243,6 +243,8 @@ class ApplicationSetting < ApplicationRecord ...@@ -243,6 +243,8 @@ class ApplicationSetting < ApplicationRecord
validates :snippet_size_limit, numericality: { only_integer: true, greater_than: 0 } validates :snippet_size_limit, numericality: { only_integer: true, greater_than: 0 }
validate :email_restrictions_regex_valid?
SUPPORTED_KEY_TYPES.each do |type| SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type } validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end end
...@@ -381,6 +383,14 @@ class ApplicationSetting < ApplicationRecord ...@@ -381,6 +383,14 @@ class ApplicationSetting < ApplicationRecord
def recaptcha_or_login_protection_enabled def recaptcha_or_login_protection_enabled
recaptcha_enabled || login_recaptcha_protection_enabled recaptcha_enabled || login_recaptcha_protection_enabled
end end
def email_restrictions_regex_valid?
return if email_restrictions.blank?
Gitlab::UntrustedRegexp.new(email_restrictions)
rescue RegexpError
errors.add(:email_restrictions, _('is not a valid regular expression'))
end
end end
ApplicationSetting.prepend_if_ee('EE::ApplicationSetting') ApplicationSetting.prepend_if_ee('EE::ApplicationSetting')
...@@ -62,6 +62,8 @@ module ApplicationSettingImplementation ...@@ -62,6 +62,8 @@ module ApplicationSettingImplementation
eks_account_id: nil, eks_account_id: nil,
eks_access_key_id: nil, eks_access_key_id: nil,
eks_secret_access_key: nil, eks_secret_access_key: nil,
email_restrictions_enabled: false,
email_restrictions: nil,
first_day_of_week: 0, first_day_of_week: 0,
gitaly_timeout_default: 55, gitaly_timeout_default: 55,
gitaly_timeout_fast: 10, gitaly_timeout_fast: 10,
......
...@@ -189,6 +189,7 @@ class User < ApplicationRecord ...@@ -189,6 +189,7 @@ class User < ApplicationRecord
validate :owns_public_email, if: :public_email_changed? validate :owns_public_email, if: :public_email_changed?
validate :owns_commit_email, if: :commit_email_changed? validate :owns_commit_email, if: :commit_email_changed?
validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id } validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
validate :check_email_restrictions, on: :create, if: ->(user) { !user.created_by_id }
validates :theme_id, allow_nil: true, inclusion: { in: Gitlab::Themes.valid_ids, validates :theme_id, allow_nil: true, inclusion: { in: Gitlab::Themes.valid_ids,
message: _("%{placeholder} is not a valid theme") % { placeholder: '%{value}' } } message: _("%{placeholder} is not a valid theme") % { placeholder: '%{value}' } }
...@@ -1747,6 +1748,18 @@ class User < ApplicationRecord ...@@ -1747,6 +1748,18 @@ class User < ApplicationRecord
end end
end end
def check_email_restrictions
return unless Feature.enabled?(:email_restrictions)
return unless Gitlab::CurrentSettings.email_restrictions_enabled?
restrictions = Gitlab::CurrentSettings.email_restrictions
return if restrictions.blank?
if Gitlab::UntrustedRegexp.new(restrictions).match?(email)
errors.add(:email, _('is not allowed for sign-up'))
end
end
def self.unique_internal(scope, username, email_pattern, &block) def self.unique_internal(scope, username, email_pattern, &block)
scope.first || create_unique_internal(scope, username, email_pattern, &block) scope.first || create_unique_internal(scope, username, email_pattern, &block)
end end
......
...@@ -49,6 +49,20 @@ ...@@ -49,6 +49,20 @@
= f.label :domain_blacklist, 'Blacklisted domains for sign-ups', class: 'label-bold' = f.label :domain_blacklist, 'Blacklisted domains for sign-ups', class: 'label-bold'
= f.text_area :domain_blacklist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8 = f.text_area :domain_blacklist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8
.form-text.text-muted Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com .form-text.text-muted Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
- if Feature.enabled?(:email_restrictions)
.form-group
= f.label :email_restrictions_enabled, _('Email restrictions'), class: 'label-bold'
.form-check
= f.check_box :email_restrictions_enabled, class: 'form-check-input'
= f.label :email_restrictions_enabled, class: 'form-check-label' do
= _('Enable email restrictions for sign ups')
.form-group
= f.label :email_restrictions, _('Email restrictions for sign-ups'), class: 'label-bold'
= f.text_area :email_restrictions, class: 'form-control', rows: 4
.form-text.text-muted
- supported_syntax_link_url = 'https://github.com/google/re2/wiki/Syntax'
- supported_syntax_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: supported_syntax_link_url }
= _('Restricts sign-ups for email addresses that match the given regex. See the %{supported_syntax_link_start}supported syntax%{supported_syntax_link_end} for more information.').html_safe % { supported_syntax_link_start: supported_syntax_link_start, supported_syntax_link_end: '</a>'.html_safe }
.form-group .form-group
= f.label :after_sign_up_text, class: 'label-bold' = f.label :after_sign_up_text, class: 'label-bold'
......
---
title: Add restrictions for signup email addresses
merge_request: 25122
author:
type: added
# frozen_string_literal: true
class AddEmailRestrictionsToApplicationSettings < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
add_column(:application_settings, :email_restrictions_enabled, :boolean, default: false, null: false)
add_column(:application_settings, :email_restrictions, :text, null: true)
end
def down
remove_column(:application_settings, :email_restrictions_enabled)
remove_column(:application_settings, :email_restrictions)
end
end
...@@ -349,6 +349,8 @@ ActiveRecord::Schema.define(version: 2020_02_13_220211) do ...@@ -349,6 +349,8 @@ ActiveRecord::Schema.define(version: 2020_02_13_220211) do
t.boolean "disable_overriding_approvers_per_merge_request", default: false, null: false t.boolean "disable_overriding_approvers_per_merge_request", default: false, null: false
t.boolean "prevent_merge_requests_author_approval", default: false, null: false t.boolean "prevent_merge_requests_author_approval", default: false, null: false
t.boolean "prevent_merge_requests_committers_approval", default: false, null: false t.boolean "prevent_merge_requests_committers_approval", default: false, null: false
t.boolean "email_restrictions_enabled", default: false, null: false
t.text "email_restrictions"
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id" t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id" t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id" t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id"
......
...@@ -7068,6 +7068,12 @@ msgstr "" ...@@ -7068,6 +7068,12 @@ msgstr ""
msgid "Email patch" msgid "Email patch"
msgstr "" msgstr ""
msgid "Email restrictions"
msgstr ""
msgid "Email restrictions for sign-ups"
msgstr ""
msgid "Email the pipelines status to a list of recipients." msgid "Email the pipelines status to a list of recipients."
msgstr "" msgstr ""
...@@ -7176,6 +7182,9 @@ msgstr "" ...@@ -7176,6 +7182,9 @@ msgstr ""
msgid "Enable classification control using an external service" msgid "Enable classification control using an external service"
msgstr "" msgstr ""
msgid "Enable email restrictions for sign ups"
msgstr ""
msgid "Enable error tracking" msgid "Enable error tracking"
msgstr "" msgstr ""
...@@ -16374,6 +16383,9 @@ msgstr "" ...@@ -16374,6 +16383,9 @@ msgstr ""
msgid "Restrict membership by email" msgid "Restrict membership by email"
msgstr "" msgstr ""
msgid "Restricts sign-ups for email addresses that match the given regex. See the %{supported_syntax_link_start}supported syntax%{supported_syntax_link_end} for more information."
msgstr ""
msgid "Resume" msgid "Resume"
msgstr "" msgstr ""
...@@ -23094,6 +23106,12 @@ msgstr "" ...@@ -23094,6 +23106,12 @@ msgstr ""
msgid "is not a valid X509 certificate." msgid "is not a valid X509 certificate."
msgstr "" msgstr ""
msgid "is not a valid regular expression"
msgstr ""
msgid "is not allowed for sign-up"
msgstr ""
msgid "is not an email you own" msgid "is not an email you own"
msgstr "" msgstr ""
......
...@@ -633,5 +633,56 @@ describe ApplicationSetting do ...@@ -633,5 +633,56 @@ describe ApplicationSetting do
end end
end end
describe 'email_restrictions' do
context 'when email restrictions are enabled' do
before do
subject.email_restrictions_enabled = true
end
it 'allows empty email restrictions' do
subject.email_restrictions = ''
expect(subject).to be_valid
end
it 'accepts valid email restrictions regex' do
subject.email_restrictions = '\+'
expect(subject).to be_valid
end
it 'does not accept invalid email restrictions regex' do
subject.email_restrictions = '+'
expect(subject).not_to be_valid
end
it 'sets an error when regex is not valid' do
subject.email_restrictions = '+'
expect(subject).not_to be_valid
expect(subject.errors.messages[:email_restrictions].first).to eq(_('is not a valid regular expression'))
end
end
context 'when email restrictions are disabled' do
before do
subject.email_restrictions_enabled = false
end
it 'allows empty email restrictions' do
subject.email_restrictions = ''
expect(subject).to be_valid
end
it 'invalid regex is not valid' do
subject.email_restrictions = '+'
expect(subject).not_to be_valid
end
end
end
it_behaves_like 'application settings examples' it_behaves_like 'application settings examples'
end end
...@@ -430,6 +430,73 @@ describe User, :do_not_mock_admin_mode do ...@@ -430,6 +430,73 @@ describe User, :do_not_mock_admin_mode do
end end
end end
context 'email restrictions' do
context 'when email restriction is disabled' do
before do
stub_application_setting(email_restrictions_enabled: false)
stub_application_setting(email_restrictions: '\+')
end
it 'does accept email address' do
user = build(:user, email: 'info+1@test.com')
expect(user).to be_valid
end
end
context 'when email restrictions is enabled' do
before do
stub_application_setting(email_restrictions_enabled: true)
stub_application_setting(email_restrictions: '([\+]|\b(\w*gitlab.com\w*)\b)')
end
it 'does not accept email address with + characters' do
user = build(:user, email: 'info+1@test.com')
expect(user).not_to be_valid
end
it 'does not accept email with a gitlab domain' do
user = build(:user, email: 'info@gitlab.com')
expect(user).not_to be_valid
end
it 'adds an error message when email is not accepted' do
user = build(:user, email: 'info@gitlab.com')
expect(user).not_to be_valid
expect(user.errors.messages[:email].first).to eq(_('is not allowed for sign-up'))
end
it 'does accept a valid email address' do
user = build(:user, email: 'info@test.com')
expect(user).to be_valid
end
context 'when feature flag is turned off' do
before do
stub_feature_flags(email_restrictions: false)
end
it 'does accept the email address' do
user = build(:user, email: 'info+1@test.com')
expect(user).to be_valid
end
end
context 'when created_by_id is set' do
it 'does accept the email address' do
user = build(:user, email: 'info+1@test.com', created_by_id: 1)
expect(user).to be_valid
end
end
end
end
context 'owns_notification_email' do context 'owns_notification_email' do
it 'accepts temp_oauth_email emails' do it 'accepts temp_oauth_email emails' do
user = build(:user, email: "temp-email-for-oauth@example.com") user = build(:user, email: "temp-email-for-oauth@example.com")
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment