summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoger Rüttimann <roger.ruettimann@gmail.com>2018-08-30 12:53:06 +0000
committerPhil Hughes <me@iamphill.com>2018-08-30 12:53:06 +0000
commit93b9bfd93a841b7f86e6aeab3f9c5e9ede3a4503 (patch)
tree8bfec898a33d9b0b1693e73ce27a61db54881a66
parent3113fb848001fdea3a039295002d7752b0feebbb (diff)
downloadgitlab-ce-93b9bfd93a841b7f86e6aeab3f9c5e9ede3a4503.tar.gz
Allow whitelisting for "external collaborator by default" setting
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock4
-rw-r--r--Gemfile.rails5.lock4
-rw-r--r--app/assets/javascripts/pages/admin/application_settings/account_and_limits.js25
-rw-r--r--app/assets/javascripts/pages/admin/index.js6
-rw-r--r--app/assets/javascripts/pages/admin/users/new/index.js49
-rw-r--r--app/helpers/application_settings_helper.rb1
-rw-r--r--app/helpers/users_helper.rb11
-rw-r--r--app/models/application_setting.rb11
-rw-r--r--app/services/users/build_service.rb12
-rw-r--r--app/validators/js_regex_validator.rb15
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml7
-rw-r--r--app/views/admin/users/_access_levels.html.haml4
-rw-r--r--changelogs/unreleased/feature-whitelist-new-users-as-internal.yml5
-rw-r--r--db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb13
-rw-r--r--db/schema.rb1
-rw-r--r--doc/user/permissions.md17
-rw-r--r--locale/gitlab.pot15
-rw-r--r--spec/features/admin/admin_settings_spec.rb12
-rw-r--r--spec/features/admin/admin_users_spec.rb46
-rw-r--r--spec/helpers/users_helper_spec.rb24
-rw-r--r--spec/javascripts/fixtures/admin_users.rb29
-rw-r--r--spec/javascripts/fixtures/application_settings.rb34
-rw-r--r--spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js33
-rw-r--r--spec/javascripts/pages/admin/users/new/index_spec.js43
-rw-r--r--spec/models/application_setting_spec.rb24
-rw-r--r--spec/services/users/build_service_spec.rb106
-rw-r--r--spec/validators/js_regex_validator_spec.rb27
28 files changed, 579 insertions, 2 deletions
diff --git a/Gemfile b/Gemfile
index 208289cb7fb..8fbdb1917c7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -195,6 +195,9 @@ gem 're2', '~> 1.1.1'
gem 'version_sorter', '~> 2.1.0'
+# Export Ruby Regex to Javascript
+gem 'js_regex', '~> 2.2.1'
+
# User agent parsing
gem 'device_detector'
diff --git a/Gemfile.lock b/Gemfile.lock
index 77effb63d2e..754b2e56de1 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -428,6 +428,8 @@ GEM
multipart-post
oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2)
+ js_regex (2.2.1)
+ regexp_parser (>= 0.4.11, <= 0.5.0)
json (1.8.6)
json-jwt (1.9.4)
activesupport
@@ -726,6 +728,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.4.1)
redis (>= 2.2, < 5)
+ regexp_parser (0.5.0)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
@@ -1074,6 +1077,7 @@ DEPENDENCIES
influxdb (~> 0.2)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
+ js_regex (~> 2.2.1)
json-schema (~> 2.8.0)
jwt (~> 1.5.6)
kaminari (~> 1.0)
diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock
index 63b450d3f62..5091c671b8b 100644
--- a/Gemfile.rails5.lock
+++ b/Gemfile.rails5.lock
@@ -431,6 +431,8 @@ GEM
multipart-post
oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2)
+ js_regex (2.2.1)
+ regexp_parser (>= 0.4.11, <= 0.5.0)
json (1.8.6)
json-jwt (1.9.4)
activesupport
@@ -735,6 +737,7 @@ GEM
redis-store (>= 1.2, < 2)
redis-store (1.4.1)
redis (>= 2.2, < 5)
+ regexp_parser (0.5.0)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
@@ -1084,6 +1087,7 @@ DEPENDENCIES
influxdb (~> 0.2)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
+ js_regex (~> 2.2.1)
json-schema (~> 2.8.0)
jwt (~> 1.5.6)
kaminari (~> 1.0)
diff --git a/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js b/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js
new file mode 100644
index 00000000000..7281f907ec7
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js
@@ -0,0 +1,25 @@
+import { __ } from '~/locale';
+
+export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE = __('Regex pattern');
+export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE = __('To define internal users, first enable new users set to external');
+
+function setUserInternalRegexPlaceholder(checkbox) {
+ const userInternalRegex = document.getElementById('application_setting_user_default_internal_regex');
+ if (checkbox && userInternalRegex) {
+ if (checkbox.checked) {
+ userInternalRegex.readOnly = false;
+ userInternalRegex.placeholder = PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE;
+ } else {
+ userInternalRegex.readOnly = true;
+ userInternalRegex.placeholder = PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE;
+ }
+ }
+}
+
+export default function initUserInternalRegexPlaceholder() {
+ const checkbox = document.getElementById('application_setting_user_default_external');
+ setUserInternalRegexPlaceholder(checkbox);
+ checkbox.addEventListener('change', () => {
+ setUserInternalRegexPlaceholder(checkbox);
+ });
+}
diff --git a/app/assets/javascripts/pages/admin/index.js b/app/assets/javascripts/pages/admin/index.js
index e50b61f09e2..3aa793e47b9 100644
--- a/app/assets/javascripts/pages/admin/index.js
+++ b/app/assets/javascripts/pages/admin/index.js
@@ -1,3 +1,7 @@
import initAdmin from './admin';
+import initUserInternalRegexPlaceholder from './application_settings/account_and_limits';
-document.addEventListener('DOMContentLoaded', initAdmin);
+document.addEventListener('DOMContentLoaded', () => {
+ initAdmin();
+ initUserInternalRegexPlaceholder();
+});
diff --git a/app/assets/javascripts/pages/admin/users/new/index.js b/app/assets/javascripts/pages/admin/users/new/index.js
new file mode 100644
index 00000000000..58bfa8d64e7
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/users/new/index.js
@@ -0,0 +1,49 @@
+import $ from 'jquery';
+
+export default class UserInternalRegexHandler {
+ constructor() {
+ this.regexPattern = $('[data-user-internal-regex-pattern]').data('user-internal-regex-pattern');
+ if (this.regexPattern && this.regexPattern !== '') {
+ this.regexOptions = $('[data-user-internal-regex-options]').data('user-internal-regex-options');
+ this.external = $('#user_external');
+ this.warningMessage = $('#warning_external_automatically_set');
+ this.addListenerToEmailField();
+ this.addListenerToUserExternalCheckbox();
+ }
+ }
+
+ addListenerToEmailField() {
+ $('#user_email').on('input', (event) => {
+ this.setExternalCheckbox(event.currentTarget.value);
+ });
+ }
+
+ addListenerToUserExternalCheckbox() {
+ this.external.on('click', () => {
+ this.warningMessage.addClass('hidden');
+ });
+ }
+
+ isEmailInternal(email) {
+ const regex = new RegExp(this.regexPattern, this.regexOptions);
+ return regex.test(email);
+ }
+
+ setExternalCheckbox(email) {
+ const isChecked = this.external.prop('checked');
+ if (this.isEmailInternal(email)) {
+ if (isChecked) {
+ this.external.prop('checked', false);
+ this.warningMessage.removeClass('hidden');
+ }
+ } else if (!isChecked) {
+ this.external.prop('checked', true);
+ this.warningMessage.addClass('hidden');
+ }
+ }
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ // eslint-disable-next-line
+ new UserInternalRegexHandler();
+});
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 1e05f07e676..684c84c3006 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -255,6 +255,7 @@ module ApplicationSettingsHelper
:instance_statistics_visibility_private,
:user_default_external,
:user_show_add_ssh_key_message,
+ :user_default_internal_regex,
:user_oauth_applications,
:version_check_enabled,
:web_ide_clientside_preview_enabled
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index ceea4384f91..2c0c4254a0c 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -23,6 +23,17 @@ module UsersHelper
profile_tabs.include?(tab)
end
+ def user_internal_regex_data
+ settings = Gitlab::CurrentSettings.current_application_settings
+
+ pattern, options = if settings.user_default_internal_regex_enabled?
+ regex = settings.user_default_internal_regex_instance
+ JsRegex.new(regex).to_h.slice(:source, :options).values
+ end
+
+ { user_internal_regex_pattern: pattern, user_internal_regex_options: options }
+ end
+
def current_user_menu_items
@current_user_menu_items ||= get_current_user_menu_items
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index c77faa4b71d..03bd7fa016e 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -192,6 +192,8 @@ class ApplicationSetting < ActiveRecord::Base
numericality: { less_than_or_equal_to: :gitaly_timeout_default },
if: :gitaly_timeout_default
+ validates :user_default_internal_regex, js_regex: true, allow_nil: true
+
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
@@ -299,6 +301,7 @@ class ApplicationSetting < ActiveRecord::Base
usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
instance_statistics_visibility_private: false,
user_default_external: false,
+ user_default_internal_regex: nil,
user_show_add_ssh_key_message: true
}
end
@@ -435,6 +438,14 @@ class ApplicationSetting < ActiveRecord::Base
password_authentication_enabled_for_web? || password_authentication_enabled_for_git?
end
+ def user_default_internal_regex_enabled?
+ user_default_external? && user_default_internal_regex.present?
+ end
+
+ def user_default_internal_regex_instance
+ Regexp.new(user_default_internal_regex, Regexp::IGNORECASE)
+ end
+
delegate :terms, to: :latest_terms, allow_nil: true
def latest_terms
@latest_terms ||= Term.latest
diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb
index acc2fa153ae..9417c63c43a 100644
--- a/app/services/users/build_service.rb
+++ b/app/services/users/build_service.rb
@@ -2,6 +2,10 @@
module Users
class BuildService < BaseService
+ delegate :user_default_internal_regex_enabled?,
+ :user_default_internal_regex_instance,
+ to: :'Gitlab::CurrentSettings.current_application_settings'
+
def initialize(current_user, params = {})
@current_user = current_user
@params = params.dup
@@ -89,6 +93,10 @@ module Users
if params[:reset_password]
user_params.merge!(force_random_password: true, password_expires_at: nil)
end
+
+ if user_default_internal_regex_enabled? && !user_params.key?(:external)
+ user_params[:external] = user_external?
+ end
else
allowed_signup_params = signup_params
allowed_signup_params << :skip_confirmation if skip_authorization
@@ -105,5 +113,9 @@ module Users
def skip_user_confirmation_email_from_setting
!Gitlab::CurrentSettings.send_user_confirmation_email
end
+
+ def user_external?
+ user_default_internal_regex_instance.match(params[:email]).nil?
+ end
end
end
diff --git a/app/validators/js_regex_validator.rb b/app/validators/js_regex_validator.rb
new file mode 100644
index 00000000000..a515af7b919
--- /dev/null
+++ b/app/validators/js_regex_validator.rb
@@ -0,0 +1,15 @@
+class JsRegexValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ return true if value.blank?
+
+ parsed_regex = JsRegex.new(Regexp.new(value, Regexp::IGNORECASE))
+
+ if parsed_regex.source.empty?
+ record.errors.add(attribute, "Regex Pattern #{value} can not be expressed in Javascript")
+ else
+ parsed_regex.warnings.each { |warning| record.errors.add(attribute, warning) }
+ end
+ rescue RegexpError => regex_error
+ record.errors.add(attribute, regex_error.to_s)
+ end
+end
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
index 622cb11010e..9121e44d31b 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -29,6 +29,13 @@
= f.check_box :user_default_external, class: 'form-check-input'
= f.label :user_default_external, class: 'form-check-label' do
Newly registered users will by default be external
+ .prepend-top-10
+ = _('Internal users')
+ = f.text_field :user_default_internal_regex, placeholder: _('Regex pattern'), class: 'form-control prepend-top-5'
+ .help-block
+ = _('Specify an e-mail address regex pattern to identify default internal users.')
+ = link_to _('More information'), help_page_path('user/permissions', anchor: 'external-users-permissions'),
+ target: '_blank'
.form-group
= f.label :user_show_add_ssh_key_message, 'Prompt users to upload SSH keys', class: 'label-bold'
.form-check
diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml
index 5f68163163e..12e24ddef02 100644
--- a/app/views/admin/users/_access_levels.html.haml
+++ b/app/views/admin/users/_access_levels.html.haml
@@ -34,8 +34,12 @@
.form-group.row
.col-sm-2.text-right
= f.label :external, class: 'col-form-label'
+ .hidden{ data: user_internal_regex_data }
.col-sm-10
= f.check_box :external do
External
%p.light
External users cannot see internal or private projects unless access is explicitly granted. Also, external users cannot create projects or groups.
+ %row.hidden#warning_external_automatically_set.hidden
+ .badge.badge-warning.text-white
+ = _('Automatically marked as default internal user')
diff --git a/changelogs/unreleased/feature-whitelist-new-users-as-internal.yml b/changelogs/unreleased/feature-whitelist-new-users-as-internal.yml
new file mode 100644
index 00000000000..7a3bd11c119
--- /dev/null
+++ b/changelogs/unreleased/feature-whitelist-new-users-as-internal.yml
@@ -0,0 +1,5 @@
+---
+title: Add an option to whitelist users based on email address as internal when the "New user set to external" setting is enabled.
+merge_request: 17711
+author: Roger Rüttimann
+type: added
diff --git a/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb b/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb
new file mode 100644
index 00000000000..fe50e909563
--- /dev/null
+++ b/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb
@@ -0,0 +1,13 @@
+class AddUserInternalRegexToApplicationSetting < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ add_column :application_settings, :user_default_internal_regex, :string, null: true
+ end
+
+ def down
+ remove_column :application_settings, :user_default_internal_regex
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index cb8f90efded..02e545bec7d 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -164,6 +164,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do
t.boolean "authorized_keys_enabled", default: true, null: false
t.string "auto_devops_domain"
t.boolean "pages_domain_verification_enabled", default: true, null: false
+ t.string "user_default_internal_regex"
t.boolean "allow_local_requests_from_hooks_and_services", default: false, null: false
t.boolean "enforce_terms", default: false
t.boolean "mirror_available", default: true, null: false
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index b6438397db8..10ac6301aa1 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -197,7 +197,7 @@ They will, like usual users, receive a role in the project or group with all
the abilities that are mentioned in the table above. They cannot however create
groups or projects, and they have the same access as logged out users in all
other cases.
-
+
An administrator can flag a user as external [through the API](../api/users.md)
or by checking the checkbox on the admin panel. As an administrator, navigate
to **Admin > Users** to create a new user or edit an existing one. There, you
@@ -206,6 +206,21 @@ will find the option to flag the user as external.
By default new users are not set as external users. This behavior can be changed
by an administrator under **Admin > Application Settings**.
+### Default internal users
+
+The "Internal users" field allows specifying an e-mail address regex pattern to identify default internal users.
+
+New users whose email address matches the regex pattern will be set to internal by default rather than an external collaborator.
+
+The regex pattern format is Ruby, but it needs to be convertible to JavaScript, and the ignore case flag will be set, e.g. "/regex pattern/i".
+
+Here are some examples:
+
+- Use `\.internal@domain\.com` to mark email addresses containing ".internal@domain.com" internal.
+- Use `^(?:(?!\.ext@domain\.com).)*$\r?` to mark users with email addresses NOT including .ext@domain.com internal.
+
+Please be aware that this regex could lead to a DOS attack, [see](https://en.wikipedia.org/wiki/ReDoS?) ReDos on Wikipedia.
+
## Auditor users **[PREMIUM ONLY]**
>[Introduced][ee-998] in [GitLab Premium][eep] 8.17.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ce5d82d479b..b5778289b50 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -727,6 +727,9 @@ msgstr ""
msgid "AutoDevOps|enable Auto DevOps"
msgstr ""
+msgid "Automatically marked as default internal user"
+msgstr ""
+
msgid "Available"
msgstr ""
@@ -3187,6 +3190,9 @@ msgstr ""
msgid "Internal - The project can be accessed by any logged in user."
msgstr ""
+msgid "Internal users"
+msgstr ""
+
msgid "Interval Pattern"
msgstr ""
@@ -4670,6 +4676,9 @@ msgid_plural "Refreshing in %d seconds to show the updated status..."
msgstr[0] ""
msgstr[1] ""
+msgid "Regex pattern"
+msgstr ""
+
msgid "Register / Sign In"
msgstr ""
@@ -5277,6 +5286,9 @@ msgstr ""
msgid "Specific Runners"
msgstr ""
+msgid "Specify an e-mail address regex pattern to identify default internal users."
+msgstr ""
+
msgid "Specify the following URL during the Runner setup:"
msgstr ""
@@ -5934,6 +5946,9 @@ msgstr ""
msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
msgstr ""
+msgid "To define internal users, first enable new users set to external"
+msgstr ""
+
msgid "To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import."
msgstr ""
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index af1c153dec8..a3229fe1741 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -78,6 +78,18 @@ describe 'Admin updates settings' do
expect(page).to have_content "Application settings saved successfully"
end
+ it 'Change New users set to external', :js do
+ user_internal_regex = find('#application_setting_user_default_internal_regex', visible: :all)
+
+ expect(user_internal_regex).to be_readonly
+ expect(user_internal_regex['placeholder']).to eq 'To define internal users, first enable new users set to external'
+
+ check 'application_setting_user_default_external'
+
+ expect(user_internal_regex).not_to be_readonly
+ expect(user_internal_regex['placeholder']).to eq 'Regex pattern'
+ end
+
it 'Change Sign-in restrictions' do
page.within('.as-signin') do
fill_in 'Home page URL', with: 'https://about.gitlab.com/'
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index b2eaeb1c487..d32f33ca1e2 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -125,6 +125,52 @@ describe "Admin::Users" do
expect(page).to have_content('Username can contain only letters, digits')
end
end
+
+ context 'with new users set to external enabled' do
+ context 'with regex to match internal user email address set', :js do
+ before do
+ stub_application_setting(user_default_external: true)
+ stub_application_setting(user_default_internal_regex: '.internal@')
+
+ visit new_admin_user_path
+ end
+
+ def expects_external_to_be_checked
+ expect(find('#user_external')).to be_checked
+ end
+
+ def expects_external_to_be_unchecked
+ expect(find('#user_external')).not_to be_checked
+ end
+
+ def expects_warning_to_be_hidden
+ expect(find('#warning_external_automatically_set', visible: :all)[:class]).to include 'hidden'
+ end
+
+ def expects_warning_to_be_shown
+ expect(find('#warning_external_automatically_set')[:class]).not_to include 'hidden'
+ end
+
+ it 'automatically unchecks external for matching email' do
+ expects_external_to_be_checked
+ expects_warning_to_be_hidden
+
+ fill_in 'user_email', with: 'test.internal@domain.ch'
+
+ expects_external_to_be_unchecked
+ expects_warning_to_be_shown
+
+ fill_in 'user_email', with: 'test@domain.ch'
+
+ expects_external_to_be_checked
+ expects_warning_to_be_hidden
+
+ uncheck 'user_external'
+
+ expects_warning_to_be_hidden
+ end
+ end
+ end
end
describe "GET /admin/users/:id" do
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index b079802cb81..34d9115a1f6 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -42,6 +42,30 @@ describe UsersHelper do
end
end
+ describe '#user_internal_regex_data' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:user_default_external, :user_default_internal_regex, :result) do
+ false | nil | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
+ false | '' | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
+ false | 'mockRegexPattern' | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
+ true | nil | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
+ true | '' | { user_internal_regex_pattern: nil, user_internal_regex_options: nil }
+ true | 'mockRegexPattern' | { user_internal_regex_pattern: 'mockRegexPattern', user_internal_regex_options: 'gi' }
+ end
+
+ with_them do
+ before do
+ stub_application_setting(user_default_external: user_default_external)
+ stub_application_setting(user_default_internal_regex: user_default_internal_regex)
+ end
+
+ subject { helper.user_internal_regex_data }
+
+ it { is_expected.to eq(result) }
+ end
+ end
+
describe '#current_user_menu_items' do
subject(:items) { helper.current_user_menu_items }
diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/javascripts/fixtures/admin_users.rb
new file mode 100644
index 00000000000..9989ac4fff2
--- /dev/null
+++ b/spec/javascripts/fixtures/admin_users.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do
+ include StubENV
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ sign_in(admin)
+ end
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('admin/users')
+ end
+
+ it 'admin/users/new_with_internal_user_regex.html.raw' do |example|
+ stub_application_setting(user_default_external: true)
+ stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?')
+
+ get :new
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/javascripts/fixtures/application_settings.rb
new file mode 100644
index 00000000000..a9d3043f73d
--- /dev/null
+++ b/spec/javascripts/fixtures/application_settings.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :controller do
+ include StubENV
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project_empty_repo, namespace: namespace, path: 'application-settings') }
+
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ sign_in(admin)
+ end
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('application_settings/')
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'application_settings/accounts_and_limit.html.raw' do |example|
+ stub_application_setting(user_default_external: false)
+
+ get :show
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js
new file mode 100644
index 00000000000..4dbfd8f0eaa
--- /dev/null
+++ b/spec/javascripts/pages/admin/application_settings/account_and_limits_spec.js
@@ -0,0 +1,33 @@
+import $ from 'jquery';
+import initUserInternalRegexPlaceholder, { PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE,
+ PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE } from '~/pages/admin/application_settings/account_and_limits';
+
+describe('AccountAndLimits', () => {
+ const FIXTURE = 'application_settings/accounts_and_limit.html.raw';
+ let $userDefaultExternal;
+ let $userInternalRegex;
+ preloadFixtures(FIXTURE);
+
+ beforeEach(() => {
+ loadFixtures(FIXTURE);
+ initUserInternalRegexPlaceholder();
+ $userDefaultExternal = $('#application_setting_user_default_external');
+ $userInternalRegex = document.querySelector('#application_setting_user_default_internal_regex');
+ });
+
+ describe('Changing of userInternalRegex when userDefaultExternal', () => {
+ it('is unchecked', () => {
+ expect($userDefaultExternal.prop('checked')).toBeFalsy();
+ expect($userInternalRegex.placeholder).toEqual(PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE);
+ expect($userInternalRegex.readOnly).toBeTruthy();
+ });
+
+ it('is checked', (done) => {
+ if (!$userDefaultExternal.prop('checked')) $userDefaultExternal.click();
+ expect($userDefaultExternal.prop('checked')).toBeTruthy();
+ expect($userInternalRegex.placeholder).toEqual(PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE);
+ expect($userInternalRegex.readOnly).toBeFalsy();
+ done();
+ });
+ });
+});
diff --git a/spec/javascripts/pages/admin/users/new/index_spec.js b/spec/javascripts/pages/admin/users/new/index_spec.js
new file mode 100644
index 00000000000..2bac3951c3a
--- /dev/null
+++ b/spec/javascripts/pages/admin/users/new/index_spec.js
@@ -0,0 +1,43 @@
+import $ from 'jquery';
+import UserInternalRegexHandler from '~/pages/admin/users/new/index';
+
+describe('UserInternalRegexHandler', () => {
+ const FIXTURE = 'admin/users/new_with_internal_user_regex.html.raw';
+ let $userExternal;
+ let $userEmail;
+ let $warningMessage;
+
+ preloadFixtures(FIXTURE);
+
+ beforeEach(() => {
+ loadFixtures(FIXTURE);
+ // eslint-disable-next-line no-new
+ new UserInternalRegexHandler();
+ $userExternal = $('#user_external');
+ $userEmail = $('#user_email');
+ $warningMessage = $('#warning_external_automatically_set');
+ if (!$userExternal.prop('checked')) $userExternal.prop('checked', 'checked');
+ });
+
+ describe('Behaviour of userExternal checkbox when', () => {
+ it('matches email as internal', (done) => {
+ expect($warningMessage.hasClass('hidden')).toBeTruthy();
+
+ $userEmail.val('test@').trigger('input');
+
+ expect($userExternal.prop('checked')).toBeFalsy();
+ expect($warningMessage.hasClass('hidden')).toBeFalsy();
+ done();
+ });
+
+ it('matches email as external', (done) => {
+ expect($warningMessage.hasClass('hidden')).toBeTruthy();
+
+ $userEmail.val('test.ext@').trigger('input');
+
+ expect($userExternal.prop('checked')).toBeTruthy();
+ expect($warningMessage.hasClass('hidden')).toBeTruthy();
+ done();
+ });
+ });
+});
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 02f74e2ea54..483cc546423 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -538,4 +538,28 @@ describe ApplicationSetting do
expect(setting.allow_signup?).to be_falsey
end
end
+
+ describe '#user_default_internal_regex_enabled?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:user_default_external, :user_default_internal_regex, :result) do
+ false | nil | false
+ false | '' | false
+ false | '^(?:(?!\.ext@).)*$\r?\n?' | false
+ true | '' | false
+ true | nil | false
+ true | '^(?:(?!\.ext@).)*$\r?\n?' | true
+ end
+
+ with_them do
+ before do
+ setting.update(user_default_external: user_default_external)
+ setting.update(user_default_internal_regex: user_default_internal_regex)
+ end
+
+ subject { setting.user_default_internal_regex_enabled? }
+
+ it { is_expected.to eq(result) }
+ end
+ end
end
diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb
index 677d4a622e1..b987fe45138 100644
--- a/spec/services/users/build_service_spec.rb
+++ b/spec/services/users/build_service_spec.rb
@@ -13,6 +13,59 @@ describe Users::BuildService do
it 'returns a valid user' do
expect(service.execute).to be_valid
end
+
+ context 'with "user_default_external" application setting' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:user_default_external, :external, :email, :user_default_internal_regex, :result) do
+ true | nil | 'fl@example.com' | nil | true
+ true | true | 'fl@example.com' | nil | true
+ true | false | 'fl@example.com' | nil | false
+
+ true | nil | 'fl@example.com' | '' | true
+ true | true | 'fl@example.com' | '' | true
+ true | false | 'fl@example.com' | '' | false
+
+ true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+
+ true | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
+
+ false | nil | 'fl@example.com' | nil | false
+ false | true | 'fl@example.com' | nil | true
+ false | false | 'fl@example.com' | nil | false
+
+ false | nil | 'fl@example.com' | '' | false
+ false | true | 'fl@example.com' | '' | true
+ false | false | 'fl@example.com' | '' | false
+
+ false | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ false | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+
+ false | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ end
+
+ with_them do
+ before do
+ stub_application_setting(user_default_external: user_default_external)
+ stub_application_setting(user_default_internal_regex: user_default_internal_regex)
+
+ params.merge!({ external: external, email: email }.compact)
+ end
+
+ subject(:user) { service.execute }
+
+ it 'correctly sets user.external' do
+ expect(user.external).to eq(result)
+ end
+ end
+ end
end
context 'with non admin user' do
@@ -50,6 +103,59 @@ describe Users::BuildService do
expect(service.execute).to be_confirmed
end
end
+
+ context 'with "user_default_external" application setting' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:user_default_external, :external, :email, :user_default_internal_regex, :result) do
+ true | nil | 'fl@example.com' | nil | true
+ true | true | 'fl@example.com' | nil | true
+ true | false | 'fl@example.com' | nil | true
+
+ true | nil | 'fl@example.com' | '' | true
+ true | true | 'fl@example.com' | '' | true
+ true | false | 'fl@example.com' | '' | true
+
+ true | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | true
+
+ true | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
+ true | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | true
+
+ false | nil | 'fl@example.com' | nil | false
+ false | true | 'fl@example.com' | nil | false
+ false | false | 'fl@example.com' | nil | false
+
+ false | nil | 'fl@example.com' | '' | false
+ false | true | 'fl@example.com' | '' | false
+ false | false | 'fl@example.com' | '' | false
+
+ false | nil | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ false | true | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ false | false | 'fl@example.com' | '^(?:(?!\.ext@).)*$\r?' | false
+
+ false | nil | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ false | true | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ false | false | 'tester.ext@domain.com' | '^(?:(?!\.ext@).)*$\r?' | false
+ end
+
+ with_them do
+ before do
+ stub_application_setting(user_default_external: user_default_external)
+ stub_application_setting(user_default_internal_regex: user_default_internal_regex)
+
+ params.merge!({ external: external, email: email }.compact)
+ end
+
+ subject(:user) { service.execute }
+
+ it 'sets the value of Gitlab::CurrentSettings.user_default_external' do
+ expect(user.external).to eq(result)
+ end
+ end
+ end
end
end
end
diff --git a/spec/validators/js_regex_validator_spec.rb b/spec/validators/js_regex_validator_spec.rb
new file mode 100644
index 00000000000..aeb55cdc0e5
--- /dev/null
+++ b/spec/validators/js_regex_validator_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe JsRegexValidator do
+ describe '#validates_each' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:validator) { described_class.new(attributes: [:user_default_internal_regex]) }
+ let(:application_setting) { build(:application_setting, user_default_external: true) }
+
+ where(:user_default_internal_regex, :result) do
+ nil | []
+ '' | []
+ '(?#comment)' | ['Regex Pattern (?#comment) can not be expressed in Javascript']
+ '(?(a)b|c)' | ['invalid conditional pattern: /(?(a)b|c)/i']
+ '[a-z&&[^uo]]' | ["Dropped unsupported set intersection '[a-z&&[^uo]]' at index 0",
+ "Dropped unsupported nested negative set data '[^uo]' at index 6"]
+ end
+
+ with_them do
+ it 'generates correct errors' do
+ validator.validate_each(application_setting, :user_default_internal_regex, user_default_internal_regex)
+
+ expect(application_setting.errors[:user_default_internal_regex]).to eq result
+ end
+ end
+ end
+end