Commit 0df317f7 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'restrict-signups-to-domains' into 'master'

Add application setting to restrict user signups to e-mail domains

This feature was requested long ago:

http://feedback.gitlab.com/forums/176466-general/suggestions/4118466-ability-to-register-only-from-ceratain-domains

This MR is based off !253 but changed to use application settings and use wildcard strings
to give more flexibility in pattern matching. Regexps seemed overkill and prone to mistakes.

Also note that validation is ONLY done on creation to prevent breaking existing users who do not have a whitelisted domain. However, this allows a user to sign-up and change his/her email to a non-whitelisted domain.

Screenshots:

![image](https://gitlab.com/gitlab-org/gitlab-ce/uploads/b312046aae03971f37f4247382971fc6/image.png)

![image](https://gitlab.com/gitlab-org/gitlab-ce/uploads/94bdf3ffaf37c2e8324eff83308f81f0/image.png)

See merge request !598
parents 0d0c539c eb4f1eb5
...@@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date.
v 7.11.0 (unreleased) v 7.11.0 (unreleased)
- Make Reply-To config apply to change e-mail confirmation and other Devise notifications (Stan Hu) - Make Reply-To config apply to change e-mail confirmation and other Devise notifications (Stan Hu)
- Add application setting to restrict user signups to e-mail domains (Stan Hu)
- Don't allow a merge request to be merged when its title starts with "WIP". - Don't allow a merge request to be merged when its title starts with "WIP".
- Add a page title to every page. - Add a page title to every page.
- Get Gitorious importer to work again. - Get Gitorious importer to work again.
......
...@@ -41,7 +41,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -41,7 +41,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:max_attachment_size, :max_attachment_size,
:default_project_visibility, :default_project_visibility,
:default_snippet_visibility, :default_snippet_visibility,
restricted_visibility_levels: [] :restricted_signup_domains_raw,
restricted_visibility_levels: [],
) )
end end
end end
...@@ -18,11 +18,13 @@ ...@@ -18,11 +18,13 @@
# restricted_visibility_levels :text # restricted_visibility_levels :text
# max_attachment_size :integer default(10) # max_attachment_size :integer default(10)
# default_project_visibility :integer # default_project_visibility :integer
# default_snippet_visibility :integer # restricted_signup_domains :text
# #
class ApplicationSetting < ActiveRecord::Base class ApplicationSetting < ActiveRecord::Base
serialize :restricted_visibility_levels serialize :restricted_visibility_levels
serialize :restricted_signup_domains, Array
attr_accessor :restricted_signup_domains_raw
validates :home_page_url, validates :home_page_url,
allow_blank: true, allow_blank: true,
...@@ -55,11 +57,29 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -55,11 +57,29 @@ class ApplicationSetting < ActiveRecord::Base
restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
max_attachment_size: Settings.gitlab['max_attachment_size'], max_attachment_size: Settings.gitlab['max_attachment_size'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'] default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
restricted_signup_domains: Settings.gitlab['restricted_signup_domains']
) )
end end
def home_page_url_column_exist def home_page_url_column_exist
ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url) ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url)
end end
def restricted_signup_domains_raw
self.restricted_signup_domains.join("\n") unless self.restricted_signup_domains.nil?
end
def restricted_signup_domains_raw=(values)
self.restricted_signup_domains = []
self.restricted_signup_domains = values.split(
/\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
| # or
\s # any whitespace character
| # or
[\r\n] # any number of newline characters
/x)
self.restricted_signup_domains.reject! { |d| d.empty? }
end
end end
...@@ -142,6 +142,7 @@ class User < ActiveRecord::Base ...@@ -142,6 +142,7 @@ class User < ActiveRecord::Base
validates :avatar, file_size: { maximum: 200.kilobytes.to_i } validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :generate_password, on: :create before_validation :generate_password, on: :create
before_validation :restricted_signup_domains, on: :create
before_validation :sanitize_attrs before_validation :sanitize_attrs
before_validation :set_notification_email, if: ->(user) { user.email_changed? } before_validation :set_notification_email, if: ->(user) { user.email_changed? }
before_validation :set_public_email, if: ->(user) { user.public_email_changed? } before_validation :set_public_email, if: ->(user) { user.public_email_changed? }
...@@ -611,4 +612,27 @@ class User < ActiveRecord::Base ...@@ -611,4 +612,27 @@ class User < ActiveRecord::Base
select(:project_id). select(:project_id).
uniq.map(&:project_id) uniq.map(&:project_id)
end end
def restricted_signup_domains
email_domains = current_application_settings.restricted_signup_domains
unless email_domains.blank?
match_found = email_domains.any? do |domain|
escaped = Regexp.escape(domain).gsub('\*','.*?')
regexp = Regexp.new "^#{escaped}$", Regexp::IGNORECASE
email_domain = Mail::Address.new(self.email).domain
email_domain =~ regexp
end
unless match_found
self.errors.add :email,
'is not whitelisted. ' +
'Email domains valid for registration are: ' +
email_domains.join(', ')
return false
end
end
true
end
end end
...@@ -72,6 +72,11 @@ ...@@ -72,6 +72,11 @@
= f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'control-label col-sm-2' = f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.number_field :max_attachment_size, class: 'form-control' = f.number_field :max_attachment_size, class: 'form-control'
.form-group
= f.label :restricted_signup_domains, 'Restricted domains for sign-ups', class: 'control-label col-sm-2'
.col-sm-10
= f.text_area :restricted_signup_domains_raw, placeholder: 'domain.com', class: 'form-control'
.help-block Ex: domain.com, *.domain.com. Wildcards allowed. Use separate lines for multiple entries.
.form-actions .form-actions
= f.submit 'Save', class: 'btn btn-primary' = f.submit 'Save', class: 'btn btn-primary'
...@@ -132,6 +132,7 @@ Settings.gitlab.default_projects_features['wiki'] = true if Settings.g ...@@ -132,6 +132,7 @@ Settings.gitlab.default_projects_features['wiki'] = true if Settings.g
Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil? Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil?
Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitlab['repository_downloads_path'] || 'tmp/repositories', Rails.root) Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitlab['repository_downloads_path'] || 'tmp/repositories', Rails.root)
Settings.gitlab['restricted_signup_domains'] ||= []
# #
# Gravatar # Gravatar
......
class AddRestrictedSignupDomainsToApplicationSettings < ActiveRecord::Migration
def change
add_column :application_settings, :restricted_signup_domains, :text
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150429002313) do ActiveRecord::Schema.define(version: 20150502064022) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -31,6 +31,7 @@ ActiveRecord::Schema.define(version: 20150429002313) do ...@@ -31,6 +31,7 @@ ActiveRecord::Schema.define(version: 20150429002313) do
t.integer "max_attachment_size", default: 10, null: false t.integer "max_attachment_size", default: 10, null: false
t.integer "default_project_visibility" t.integer "default_project_visibility"
t.integer "default_snippet_visibility" t.integer "default_snippet_visibility"
t.text "restricted_signup_domains"
end end
create_table "broadcast_messages", force: true do |t| create_table "broadcast_messages", force: true do |t|
......
...@@ -21,4 +21,28 @@ require 'spec_helper' ...@@ -21,4 +21,28 @@ require 'spec_helper'
describe ApplicationSetting, models: true do describe ApplicationSetting, models: true do
it { expect(ApplicationSetting.create_from_defaults).to be_valid } it { expect(ApplicationSetting.create_from_defaults).to be_valid }
context 'restricted signup domains' do
let(:setting) { ApplicationSetting.create_from_defaults }
it 'set single domain' do
setting.restricted_signup_domains_raw = 'example.com'
expect(setting.restricted_signup_domains).to eq(['example.com'])
end
it 'set multiple domains with spaces' do
setting.restricted_signup_domains_raw = 'example.com *.example.com'
expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com'])
end
it 'set multiple domains with newlines and a space' do
setting.restricted_signup_domains_raw = "example.com\n *.example.com"
expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com'])
end
it 'set multiple domains with commas' do
setting.restricted_signup_domains_raw = "example.com, *.example.com"
expect(setting.restricted_signup_domains).to eq(['example.com', '*.example.com'])
end
end
end end
...@@ -54,6 +54,8 @@ ...@@ -54,6 +54,8 @@
require 'spec_helper' require 'spec_helper'
describe User do describe User do
include Gitlab::CurrentSettings
describe "Associations" do describe "Associations" do
it { is_expected.to have_one(:namespace) } it { is_expected.to have_one(:namespace) }
it { is_expected.to have_many(:snippets).class_name('Snippet').dependent(:destroy) } it { is_expected.to have_many(:snippets).class_name('Snippet').dependent(:destroy) }
...@@ -112,6 +114,51 @@ describe User do ...@@ -112,6 +114,51 @@ describe User do
user = build(:user, email: "lol!'+=?><#$%^&*()@gmail.com") user = build(:user, email: "lol!'+=?><#$%^&*()@gmail.com")
expect(user).to be_invalid expect(user).to be_invalid
end end
context 'when no signup domains listed' do
before { allow(current_application_settings).to receive(:restricted_signup_domains).and_return([]) }
it 'accepts any email' do
user = build(:user, email: "info@example.com")
expect(user).to be_valid
end
end
context 'when a signup domain is listed and subdomains are allowed' do
before { allow(current_application_settings).to receive(:restricted_signup_domains).and_return(['example.com', '*.example.com']) }
it 'accepts info@example.com' do
user = build(:user, email: "info@example.com")
expect(user).to be_valid
end
it 'accepts info@test.example.com' do
user = build(:user, email: "info@test.example.com")
expect(user).to be_valid
end
it 'rejects example@test.com' do
user = build(:user, email: "example@test.com")
expect(user).to be_invalid
end
end
context 'when a signup domain is listed and subdomains are not allowed' do
before { allow(current_application_settings).to receive(:restricted_signup_domains).and_return(['example.com']) }
it 'accepts info@example.com' do
user = build(:user, email: "info@example.com")
expect(user).to be_valid
end
it 'rejects info@test.example.com' do
user = build(:user, email: "info@test.example.com")
expect(user).to be_invalid
end
it 'rejects example@test.com' do
user = build(:user, email: "example@test.com")
expect(user).to be_invalid
end
end
end end
end end
......
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