Commit 0fd9d62f authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch 'astoicescu-convert-signup-settings-rails-form-to-vue' into 'master'

Convert the sign-up settings section from Rails to Vue

See merge request gitlab-org/gitlab!57135
parents 0e7c92cc 50ea2d49
import $ from 'jquery';
import { refreshCurrentPage } from '../../lib/utils/url_utility';
function showDenylistType() {
if ($('input[name="denylist_type"]:checked').val() === 'file') {
$('.js-denylist-file').show();
$('.js-denylist-raw').hide();
} else {
$('.js-denylist-file').hide();
$('.js-denylist-raw').show();
}
}
export default function adminInit() {
$('input#user_force_random_password').on('change', function randomPasswordClick() {
const $elems = $('#user_password, #user_password_confirmation');
......@@ -27,7 +17,4 @@ export default function adminInit() {
});
$('li.project_member, li.group_member').on('ajax:success', refreshCurrentPage);
$("input[name='denylist_type']").on('click', showDenylistType);
showDenylistType();
}
<script>
import { GlFormCheckbox } from '@gitlab/ui';
export default {
components: {
GlFormCheckbox,
},
props: {
name: {
type: String,
required: true,
},
helpText: {
type: String,
required: false,
default: '',
},
label: {
type: String,
required: true,
},
value: {
type: Boolean,
required: true,
},
dataQaSelector: {
type: String,
required: false,
default: '',
},
},
};
</script>
<template>
<div>
<input :name="name" type="hidden" :value="value ? '1' : '0'" data-testid="input" />
<gl-form-checkbox
:checked="value"
:data-qa-selector="dataQaSelector"
@input="$emit('input', $event)"
>
<span data-testid="label">{{ label }}</span>
<template v-if="helpText" #help>
<span data-testid="helpText">{{ helpText }}</span>
</template>
</gl-form-checkbox>
</div>
</template>
<script>
import {
GlButton,
GlFormGroup,
GlFormInput,
GlFormRadio,
GlFormRadioGroup,
GlSprintf,
GlLink,
} from '@gitlab/ui';
import csrf from '~/lib/utils/csrf';
import { s__, sprintf } from '~/locale';
import SignupCheckbox from './signup_checkbox.vue';
const DENYLIST_TYPE_RAW = 'raw';
const DENYLIST_TYPE_FILE = 'file';
export default {
csrf,
DENYLIST_TYPE_RAW,
DENYLIST_TYPE_FILE,
components: {
GlButton,
GlFormGroup,
GlFormInput,
GlFormRadio,
GlFormRadioGroup,
GlSprintf,
GlLink,
SignupCheckbox,
},
inject: [
'host',
'settingsPath',
'signupEnabled',
'requireAdminApprovalAfterUserSignup',
'sendUserConfirmationEmail',
'minimumPasswordLength',
'minimumPasswordLengthMin',
'minimumPasswordLengthMax',
'minimumPasswordLengthHelpLink',
'domainAllowlistRaw',
'newUserSignupsCap',
'domainDenylistEnabled',
'denylistTypeRawSelected',
'domainDenylistRaw',
'emailRestrictionsEnabled',
'supportedSyntaxLinkUrl',
'emailRestrictions',
'afterSignUpText',
],
data() {
return {
form: {
signupEnabled: this.signupEnabled,
requireAdminApproval: this.requireAdminApprovalAfterUserSignup,
sendConfirmationEmail: this.sendUserConfirmationEmail,
minimumPasswordLength: this.minimumPasswordLength,
minimumPasswordLengthMin: this.minimumPasswordLengthMin,
minimumPasswordLengthMax: this.minimumPasswordLengthMax,
minimumPasswordLengthHelpLink: this.minimumPasswordLengthHelpLink,
domainAllowlistRaw: this.domainAllowlistRaw,
userCap: this.newUserSignupsCap,
domainDenylistEnabled: this.domainDenylistEnabled,
denylistType: this.denylistTypeRawSelected
? this.$options.DENYLIST_TYPE_RAW
: this.$options.DENYLIST_TYPE_FILE,
domainDenylistRaw: this.domainDenylistRaw,
emailRestrictionsEnabled: this.emailRestrictionsEnabled,
supportedSyntaxLinkUrl: this.supportedSyntaxLinkUrl,
emailRestrictions: this.emailRestrictions,
afterSignUpText: this.afterSignUpText,
},
};
},
computed: {
signupEnabledHelpText() {
const text = sprintf(
s__(
'ApplicationSettings|When enabled, any user visiting %{host} will be able to create an account.',
),
{
host: this.host,
},
);
return text;
},
requireAdminApprovalHelpText() {
const text = sprintf(
s__(
'ApplicationSettings|When enabled, any user visiting %{host} and creating an account will have to be explicitly approved by an admin before they can sign in. This setting is effective only if sign-ups are enabled.',
),
{
host: this.host,
},
);
return text;
},
},
methods: {
submitButtonHandler() {
this.$refs.form.submit();
},
},
i18n: {
buttonText: s__('ApplicationSettings|Save changes'),
signupEnabledLabel: s__('ApplicationSettings|Sign-up enabled'),
requireAdminApprovalLabel: s__('ApplicationSettings|Require admin approval for new sign-ups'),
sendConfirmationEmailLabel: s__('ApplicationSettings|Send confirmation email on sign-up'),
minimumPasswordLengthLabel: s__(
'ApplicationSettings|Minimum password length (number of characters)',
),
domainAllowListLabel: s__('ApplicationSettings|Allowed domains for sign-ups'),
domainAllowListDescription: s__(
'ApplicationSettings|ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com',
),
userCapLabel: s__('ApplicationSettings|User cap'),
userCapDescription: s__(
'ApplicationSettings|Once the instance reaches the user cap, any user who is added or requests access will have to be approved by an admin. Leave the field empty for unlimited.',
),
domainDenyListGroupLabel: s__('ApplicationSettings|Domain denylist'),
domainDenyListLabel: s__('ApplicationSettings|Enable domain denylist for sign ups'),
domainDenyListTypeFileLabel: s__('ApplicationSettings|Upload denylist file'),
domainDenyListTypeRawLabel: s__('ApplicationSettings|Enter denylist manually'),
domainDenyListFileLabel: s__('ApplicationSettings|Denylist file'),
domainDenyListFileDescription: s__(
'ApplicationSettings|Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries.',
),
domainDenyListListLabel: s__('ApplicationSettings|Denied domains for sign-ups'),
domainDenyListListDescription: s__(
'ApplicationSettings|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',
),
domainPlaceholder: s__('ApplicationSettings|domain.com'),
emailRestrictionsEnabledGroupLabel: s__('ApplicationSettings|Email restrictions'),
emailRestrictionsEnabledLabel: s__(
'ApplicationSettings|Enable email restrictions for sign ups',
),
emailRestrictionsGroupLabel: s__('ApplicationSettings|Email restrictions for sign-ups'),
afterSignUpTextGroupLabel: s__('ApplicationSettings|After sign up text'),
afterSignUpTextGroupDescription: s__('ApplicationSettings|Markdown enabled'),
},
};
</script>
<template>
<form
ref="form"
accept-charset="UTF-8"
data-testid="form"
method="post"
:action="settingsPath"
enctype="multipart/form-data"
>
<input type="hidden" name="utf8" value="✓" />
<input type="hidden" name="_method" value="patch" />
<input type="hidden" name="authenticity_token" :value="$options.csrf.token" />
<section class="gl-mb-8">
<signup-checkbox
v-model="form.signupEnabled"
class="gl-mb-5"
name="application_setting[signup_enabled]"
:help-text="signupEnabledHelpText"
:label="$options.i18n.signupEnabledLabel"
data-qa-selector="signup_enabled_checkbox"
/>
<signup-checkbox
v-model="form.requireAdminApproval"
class="gl-mb-5"
name="application_setting[require_admin_approval_after_user_signup]"
:help-text="requireAdminApprovalHelpText"
:label="$options.i18n.requireAdminApprovalLabel"
data-qa-selector="require_admin_approval_after_user_signup_checkbox"
/>
<signup-checkbox
v-model="form.sendConfirmationEmail"
class="gl-mb-5"
name="application_setting[send_user_confirmation_email]"
:label="$options.i18n.sendConfirmationEmailLabel"
/>
<gl-form-group
:label="$options.i18n.userCapLabel"
:description="$options.i18n.userCapDescription"
>
<gl-form-input
v-model="form.userCap"
type="text"
name="application_setting[new_user_signups_cap]"
/>
</gl-form-group>
<gl-form-group :label="$options.i18n.minimumPasswordLengthLabel">
<gl-form-input
v-model="form.minimumPasswordLength"
:min="form.minimumPasswordLengthMin"
:max="form.minimumPasswordLengthMax"
type="number"
name="application_setting[minimum_password_length]"
/>
<gl-sprintf
:message="
s__(
'ApplicationSettings|See GitLab\'s %{linkStart}Password Policy Guidelines%{linkEnd}',
)
"
>
<template #link="{ content }">
<gl-link :href="form.minimumPasswordLengthHelpLink" target="_blank">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</gl-form-group>
<gl-form-group
:description="$options.i18n.domainAllowListDescription"
:label="$options.i18n.domainAllowListLabel"
>
<textarea
v-model="form.domainAllowlistRaw"
:placeholder="$options.i18n.domainPlaceholder"
rows="8"
class="form-control gl-form-input"
name="application_setting[domain_allowlist_raw]"
></textarea>
</gl-form-group>
<gl-form-group :label="$options.i18n.domainDenyListGroupLabel">
<signup-checkbox
v-model="form.domainDenylistEnabled"
name="application_setting[domain_denylist_enabled]"
:label="$options.i18n.domainDenyListLabel"
/>
</gl-form-group>
<gl-form-radio-group v-model="form.denylistType" name="denylist_type" class="gl-mb-5">
<gl-form-radio :value="$options.DENYLIST_TYPE_FILE">{{
$options.i18n.domainDenyListTypeFileLabel
}}</gl-form-radio>
<gl-form-radio :value="$options.DENYLIST_TYPE_RAW">{{
$options.i18n.domainDenyListTypeRawLabel
}}</gl-form-radio>
</gl-form-radio-group>
<gl-form-group
v-if="form.denylistType === $options.DENYLIST_TYPE_FILE"
:description="$options.i18n.domainDenyListFileDescription"
:label="$options.i18n.domainDenyListFileLabel"
label-for="domain-denylist-file-input"
data-testid="domain-denylist-file-input-group"
>
<input
id="domain-denylist-file-input"
class="form-control gl-form-input"
type="file"
accept=".txt,.conf"
name="application_setting[domain_denylist_file]"
/>
</gl-form-group>
<gl-form-group
v-if="form.denylistType !== $options.DENYLIST_TYPE_FILE"
:description="$options.i18n.domainDenyListListDescription"
:label="$options.i18n.domainDenyListListLabel"
data-testid="domain-denylist-raw-input-group"
>
<textarea
v-model="form.domainDenylistRaw"
:placeholder="$options.i18n.domainPlaceholder"
rows="8"
class="form-control gl-form-input"
name="application_setting[domain_denylist_raw]"
></textarea>
</gl-form-group>
<gl-form-group :label="$options.i18n.emailRestrictionsEnabledGroupLabel">
<signup-checkbox
v-model="form.emailRestrictionsEnabled"
name="application_setting[email_restrictions_enabled]"
:label="$options.i18n.emailRestrictionsEnabledLabel"
/>
</gl-form-group>
<gl-form-group :label="$options.i18n.emailRestrictionsGroupLabel">
<textarea
v-model="form.emailRestrictions"
rows="4"
class="form-control gl-form-input"
name="application_setting[email_restrictions]"
></textarea>
<gl-sprintf
:message="
s__(
'ApplicationSettings|Restricts sign-ups for email addresses that match the given regex. See the %{linkStart}supported syntax%{linkEnd} for more information.',
)
"
>
<template #link="{ content }">
<gl-link :href="form.supportedSyntaxLinkUrl" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</gl-form-group>
<gl-form-group
:label="$options.i18n.afterSignUpTextGroupLabel"
:description="$options.i18n.afterSignUpTextGroupDescription"
>
<textarea
v-model="form.afterSignUpText"
rows="4"
class="form-control gl-form-input"
name="application_setting[after_sign_up_text]"
></textarea>
</gl-form-group>
</section>
<gl-button
data-qa-selector="save_changes_button"
variant="confirm"
@click="submitButtonHandler"
>
{{ $options.i18n.buttonText }}
</gl-button>
</form>
</template>
import Vue from 'vue';
import IntegrationHelpText from '~/vue_shared/components/integrations_help_text.vue';
import initUserInternalRegexPlaceholder from '../account_and_limits';
import initGitpod from '../gitpod';
import initSignupRestrictions from '../signup_restrictions';
(() => {
initUserInternalRegexPlaceholder();
const el = document.querySelector('#js-gitpod-settings-help-text');
if (!el) {
return;
}
const { message, messageUrl } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
el,
render(createElement) {
return createElement(IntegrationHelpText, {
props: {
message,
messageUrl,
},
});
},
});
initGitpod();
initSignupRestrictions();
})();
import Vue from 'vue';
import IntegrationHelpText from '~/vue_shared/components/integrations_help_text.vue';
export default function initGitpod() {
const el = document.querySelector('#js-gitpod-settings-help-text');
if (!el) {
return false;
}
const { message, messageUrl } = el.dataset;
return new Vue({
el,
render(createElement) {
return createElement(IntegrationHelpText, {
props: {
message,
messageUrl,
},
});
},
});
}
import Vue from 'vue';
import SignupForm from './general/components/signup_form.vue';
import { getParsedDataset } from './utils';
export default function initSignupRestrictions(elementSelector = '#js-signup-form') {
const el = document.querySelector(elementSelector);
if (!el) {
return false;
}
const parsedDataset = getParsedDataset({
dataset: el.dataset,
booleanAttributes: [
'signupEnabled',
'requireAdminApprovalAfterUserSignup',
'sendUserConfirmationEmail',
'domainDenylistEnabled',
'denylistTypeRawSelected',
'emailRestrictionsEnabled',
],
});
return new Vue({
el,
provide: {
...parsedDataset,
},
render: (createElement) => createElement(SignupForm),
});
}
import { includes } from 'lodash';
import { parseBoolean } from '~/lib/utils/common_utils';
/**
* Returns a new dataset that has all the values of keys indicated in
* booleanAttributes transformed by the parseBoolean() helper function
*
* @param {Object}
* @returns {Object}
*/
export const getParsedDataset = ({ dataset = {}, booleanAttributes = [] } = {}) => {
const parsedDataset = {};
Object.keys(dataset).forEach((key) => {
parsedDataset[key] = includes(booleanAttributes, key)
? parseBoolean(dataset[key])
: dataset[key];
});
return parsedDataset;
};
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-signup-settings'), html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting)
= form_errors(@application_setting)
%fieldset
.form-group
.form-check
= f.check_box :signup_enabled, class: 'form-check-input', data: { qa_selector: 'signup_enabled_checkbox' }
= f.label :signup_enabled, class: 'form-check-label' do
Sign-up enabled
.form-text.text-muted
= _("When enabled, any user visiting %{host} will be able to create an account.") % { host: "#{new_user_session_url(host: Gitlab.config.gitlab.host)}" }
.form-group
.form-check
= f.check_box :require_admin_approval_after_user_signup, class: 'form-check-input', data: { qa_selector: 'require_admin_approval_after_user_signup_checkbox' }
= f.label :require_admin_approval_after_user_signup, class: 'form-check-label' do
= _('Require admin approval for new sign-ups')
.form-text.text-muted
= _("When enabled, any user visiting %{host} and creating an account will have to be explicitly approved by an admin before they can sign in. This setting is effective only if sign-ups are enabled.") % { host: "#{new_user_session_url(host: Gitlab.config.gitlab.host)}" }
.form-group
.form-check
= f.check_box :send_user_confirmation_email, class: 'form-check-input'
= f.label :send_user_confirmation_email, class: 'form-check-label' do
Send confirmation email on sign-up
= render_if_exists 'admin/application_settings/new_user_signups_cap', form: f
.form-group
= f.label :minimum_password_length, _('Minimum password length (number of characters)'), class: 'label-bold'
= f.number_field :minimum_password_length, class: 'form-control gl-form-input', rows: 4, min: ApplicationSetting::DEFAULT_MINIMUM_PASSWORD_LENGTH, max: Devise.password_length.max
- password_policy_guidelines_link = link_to _('Password Policy Guidelines'), 'https://about.gitlab.com/handbook/security/#gitlab-password-policy-guidelines', target: '_blank', rel: 'noopener noreferrer nofollow'
.form-text.text-muted
= _("See GitLab's %{password_policy_guidelines}").html_safe % { password_policy_guidelines: password_policy_guidelines_link }
.form-group
= f.label :domain_allowlist, _('Allowed domains for sign-ups'), class: 'label-bold'
= f.text_area :domain_allowlist_raw, placeholder: 'domain.com', class: 'form-control gl-form-input', rows: 8
.form-text.text-muted ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
.form-group
= f.label :domain_denylist_enabled, _('Domain denylist'), class: 'label-bold'
.form-check
= f.check_box :domain_denylist_enabled, class: 'form-check-input'
= f.label :domain_denylist_enabled, class: 'form-check-label' do
Enable domain denylist for sign ups
.form-group
.form-check
= radio_button_tag :denylist_type, :file, false, class: 'form-check-input'
= label_tag :denylist_type_file, class: 'form-check-label' do
.option-title
Upload denylist file
.form-check
= radio_button_tag :denylist_type, :raw, @application_setting.domain_denylist.present? || @application_setting.domain_denylist.blank?, class: 'form-check-input'
= label_tag :denylist_type_raw, class: 'form-check-label' do
.option-title
Enter denylist manually
.form-group.js-denylist-file
= f.label :domain_denylist_file, _('Denylist file'), class: 'label-bold'
= f.file_field :domain_denylist_file, class: 'form-control gl-form-input', accept: '.txt,.conf'
.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 or commas for multiple entries.
.form-group.js-denylist-raw
= f.label :domain_denylist, _('Denied domains for sign-ups'), class: 'label-bold'
= f.text_area :domain_denylist_raw, placeholder: 'domain.com', class: 'form-control gl-form-input', 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-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 gl-form-input', 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
= f.label :after_sign_up_text, class: 'label-bold'
= f.text_area :after_sign_up_text, class: 'form-control gl-form-input', rows: 4
.form-text.text-muted Markdown enabled
= f.submit 'Save changes', class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }
#js-signup-form{ data: { host: new_user_session_url(host: Gitlab.config.gitlab.host),
settings_path: general_admin_application_settings_path(anchor: 'js-signup-settings'),
signup_enabled: @application_setting[:signup_enabled].to_s,
require_admin_approval_after_user_signup: @application_setting[:require_admin_approval_after_user_signup].to_s,
send_user_confirmation_email: @application_setting[:send_user_confirmation_email].to_s,
minimum_password_length: @application_setting[:minimum_password_length],
minimum_password_length_min: ApplicationSetting::DEFAULT_MINIMUM_PASSWORD_LENGTH,
minimum_password_length_max: Devise.password_length.max,
minimum_password_length_help_link: 'https://about.gitlab.com/handbook/security/#gitlab-password-policy-guidelines',
domain_allowlist_raw: @application_setting.domain_allowlist_raw,
new_user_signups_cap: @application_setting[:new_user_signups_cap].to_s,
domain_denylist_enabled: @application_setting[:domain_denylist_enabled].to_s,
denylist_type_raw_selected: (@application_setting.domain_denylist.present? || @application_setting.domain_denylist.blank?).to_s,
domain_denylist_raw: @application_setting.domain_denylist_raw,
email_restrictions_enabled: @application_setting[:email_restrictions_enabled].to_s,
supported_syntax_link_url: 'https://github.com/google/re2/wiki/Syntax',
email_restrictions: @application_setting.email_restrictions,
after_sign_up_text: @application_setting[:after_sign_up_text] } }
.form-group
= form.label :new_user_signups_cap, s_('AdminArea|User cap'), class: 'label-bold'
= form.number_field :new_user_signups_cap, class: 'form-control gl-form-input', max: License.current&.restricted_user_count
.form-text.text-muted
= s_('AdminArea|Once the instance reaches the user cap, any user who is added or requests access will have to be approved by an admin. Leave the field empty for unlimited.')
......@@ -280,7 +280,7 @@ RSpec.describe 'Admin updates EE-only settings' do
end
end
context 'sign up settings' do
context 'sign up settings', :js do
context 'when license has active user count' do
let(:license) { create(:license, restrictions: { active_user_count: 1 }) }
......@@ -288,18 +288,17 @@ RSpec.describe 'Admin updates EE-only settings' do
allow(License).to receive(:current).and_return(license)
end
it 'disallows entering user cap greater then license allows', :js do
it 'disallows entering user cap greater then license allows' do
visit general_admin_application_settings_path
page.within('#js-signup-settings') do
fill_in 'User cap', with: 5
fill_in 'application_setting[new_user_signups_cap]', with: 5
click_button 'Save changes'
message =
page.find('#application_setting_new_user_signups_cap').native.attribute('validationMessage')
expect(message).to eq('Value must be less than or equal to 1.')
page.within '#error_explanation' do
expect(page).to have_text('New user signups cap must be less than or equal to 1')
end
end
end
end
......@@ -310,7 +309,7 @@ RSpec.describe 'Admin updates EE-only settings' do
expect(current_settings.new_user_signups_cap).to be_nil
page.within('#js-signup-settings') do
fill_in 'User cap', with: 5
fill_in 'application_setting[new_user_signups_cap]', with: 5
click_button 'Save changes'
......@@ -322,7 +321,7 @@ RSpec.describe 'Admin updates EE-only settings' do
visit general_admin_application_settings_path
page.within('#js-signup-settings') do
fill_in 'User cap', with: nil
fill_in 'application_setting[new_user_signups_cap]', with: nil
click_button 'Save changes'
......
......@@ -2220,9 +2220,6 @@ msgstr ""
msgid "AdminArea|New user"
msgstr ""
msgid "AdminArea|Once the instance reaches the user cap, any user who is added or requests access will have to be approved by an admin. Leave the field empty for unlimited."
msgstr ""
msgid "AdminArea|Owner"
msgstr ""
......@@ -2247,9 +2244,6 @@ msgstr ""
msgid "AdminArea|Total users"
msgstr ""
msgid "AdminArea|User cap"
msgstr ""
msgid "AdminArea|Users"
msgstr ""
......@@ -3242,9 +3236,6 @@ msgstr ""
msgid "Allowed Geo IP"
msgstr ""
msgid "Allowed domains for sign-ups"
msgstr ""
msgid "Allowed email domain restriction only permitted for top-level groups"
msgstr ""
......@@ -3854,6 +3845,87 @@ msgstr ""
msgid "Application: %{name}"
msgstr ""
msgid "ApplicationSettings|After sign up text"
msgstr ""
msgid "ApplicationSettings|Allowed domains for sign-ups"
msgstr ""
msgid "ApplicationSettings|Denied domains for sign-ups"
msgstr ""
msgid "ApplicationSettings|Denylist file"
msgstr ""
msgid "ApplicationSettings|Domain denylist"
msgstr ""
msgid "ApplicationSettings|Email restrictions"
msgstr ""
msgid "ApplicationSettings|Email restrictions for sign-ups"
msgstr ""
msgid "ApplicationSettings|Enable domain denylist for sign ups"
msgstr ""
msgid "ApplicationSettings|Enable email restrictions for sign ups"
msgstr ""
msgid "ApplicationSettings|Enter denylist manually"
msgstr ""
msgid "ApplicationSettings|Markdown enabled"
msgstr ""
msgid "ApplicationSettings|Minimum password length (number of characters)"
msgstr ""
msgid "ApplicationSettings|ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com"
msgstr ""
msgid "ApplicationSettings|Once the instance reaches the user cap, any user who is added or requests access will have to be approved by an admin. Leave the field empty for unlimited."
msgstr ""
msgid "ApplicationSettings|Require admin approval for new sign-ups"
msgstr ""
msgid "ApplicationSettings|Restricts sign-ups for email addresses that match the given regex. See the %{linkStart}supported syntax%{linkEnd} for more information."
msgstr ""
msgid "ApplicationSettings|Save changes"
msgstr ""
msgid "ApplicationSettings|See GitLab's %{linkStart}Password Policy Guidelines%{linkEnd}"
msgstr ""
msgid "ApplicationSettings|Send confirmation email on sign-up"
msgstr ""
msgid "ApplicationSettings|Sign-up enabled"
msgstr ""
msgid "ApplicationSettings|Upload denylist file"
msgstr ""
msgid "ApplicationSettings|User cap"
msgstr ""
msgid "ApplicationSettings|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"
msgstr ""
msgid "ApplicationSettings|Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries."
msgstr ""
msgid "ApplicationSettings|When enabled, any user visiting %{host} and creating an account will have to be explicitly approved by an admin before they can sign in. This setting is effective only if sign-ups are enabled."
msgstr ""
msgid "ApplicationSettings|When enabled, any user visiting %{host} will be able to create an account."
msgstr ""
msgid "ApplicationSettings|domain.com"
msgstr ""
msgid "Applications"
msgstr ""
......@@ -10409,18 +10481,12 @@ msgstr ""
msgid "Denied authorization of chat nickname %{user_name}."
msgstr ""
msgid "Denied domains for sign-ups"
msgstr ""
msgid "Deny"
msgstr ""
msgid "Deny access request"
msgstr ""
msgid "Denylist file"
msgstr ""
msgid "Dependencies"
msgstr ""
......@@ -11310,9 +11376,6 @@ msgstr ""
msgid "Domain cannot be deleted while associated to one or more clusters."
msgstr ""
msgid "Domain denylist"
msgstr ""
msgid "Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled"
msgstr ""
......@@ -11640,12 +11703,6 @@ msgstr ""
msgid "Email patch"
msgstr ""
msgid "Email restrictions"
msgstr ""
msgid "Email restrictions for sign-ups"
msgstr ""
msgid "Email sent"
msgstr ""
......@@ -11811,9 +11868,6 @@ msgstr ""
msgid "Enable container expiration and retention policies for projects created earlier than GitLab 12.7."
msgstr ""
msgid "Enable email restrictions for sign ups"
msgstr ""
msgid "Enable error tracking"
msgstr ""
......@@ -20386,9 +20440,6 @@ msgstr ""
msgid "Minimum interval in days"
msgstr ""
msgid "Minimum password length (number of characters)"
msgstr ""
msgid "Minutes"
msgstr ""
......@@ -22722,9 +22773,6 @@ msgstr ""
msgid "Password (optional)"
msgstr ""
msgid "Password Policy Guidelines"
msgstr ""
msgid "Password authentication is unavailable."
msgstr ""
......@@ -26658,9 +26706,6 @@ msgstr ""
msgid "Require additional authentication for administrative tasks"
msgstr ""
msgid "Require admin approval for new sign-ups"
msgstr ""
msgid "Require all users in this group to setup Two-factor authentication"
msgstr ""
......@@ -26860,9 +26905,6 @@ msgstr ""
msgid "Restricted shift times are not available for hourly shifts"
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"
msgstr ""
......@@ -27978,9 +28020,6 @@ msgstr ""
msgid "SecurityReports|Your feedback is important to us! We will ask again in a week."
msgstr ""
msgid "See GitLab's %{password_policy_guidelines}"
msgstr ""
msgid "See metrics"
msgstr ""
......@@ -35093,12 +35132,6 @@ msgstr ""
msgid "When an event in GitLab triggers a webhook, you can use the request details to figure out if something went wrong."
msgstr ""
msgid "When enabled, any user visiting %{host} and creating an account will have to be explicitly approved by an admin before they can sign in. This setting is effective only if sign-ups are enabled."
msgstr ""
msgid "When enabled, any user visiting %{host} will be able to create an account."
msgstr ""
msgid "When enabled, if an npm package isn't found in the GitLab Registry, we will attempt to pull from the global npm registry."
msgstr ""
......
......@@ -6,19 +6,19 @@ module QA
module Settings
module Component
class SignUpRestrictions < Page::Base
view 'app/views/admin/application_settings/_signup.html.haml' do
view 'app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue' do
element :require_admin_approval_after_user_signup_checkbox
element :signup_enabled_checkbox
element :save_changes_button
end
def require_admin_approval_after_user_signup
check_element(:require_admin_approval_after_user_signup_checkbox)
click_element_coordinates(:require_admin_approval_after_user_signup_checkbox, visible: false)
click_element(:save_changes_button)
end
def disable_signups
uncheck_element(:signup_enabled_checkbox)
click_element_coordinates(:signup_enabled_checkbox, visible: false)
click_element(:save_changes_button)
end
end
......
......@@ -129,7 +129,7 @@ RSpec.describe 'Admin updates settings' do
context 'Change Sign-up restrictions' do
context 'Require Admin approval for new signup setting' do
it 'changes the setting' do
it 'changes the setting', :js do
page.within('.as-signup') do
check 'Require admin approval for new sign-ups'
click_button 'Save changes'
......
import { GlFormCheckbox } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SignupCheckbox from '~/pages/admin/application_settings/general/components/signup_checkbox.vue';
describe('Signup Form', () => {
let wrapper;
const props = {
name: 'name',
helpText: 'some help text',
label: 'a label',
value: true,
dataQaSelector: 'qa_selector',
};
const mountComponent = () => {
wrapper = shallowMount(SignupCheckbox, {
propsData: props,
stubs: {
GlFormCheckbox,
},
});
};
const findByTestId = (id) => wrapper.find(`[data-testid="${id}"]`);
const findHiddenInput = () => findByTestId('input');
const findCheckbox = () => wrapper.find(GlFormCheckbox);
const findCheckboxLabel = () => findByTestId('label');
const findHelpText = () => findByTestId('helpText');
afterEach(() => {
wrapper.destroy();
});
describe('Signup Checkbox', () => {
beforeEach(() => {
mountComponent();
});
describe('hidden input element', () => {
it('gets passed correct values from props', () => {
expect(findHiddenInput().attributes('name')).toBe(props.name);
expect(findHiddenInput().attributes('value')).toBe('1');
});
});
describe('checkbox', () => {
it('gets passed correct checked value', () => {
expect(findCheckbox().attributes('checked')).toBe('true');
});
it('gets passed correct label', () => {
expect(findCheckboxLabel().text()).toBe(props.label);
});
it('gets passed correct help text', () => {
expect(findHelpText().text()).toBe(props.helpText);
});
it('gets passed data qa selector', () => {
expect(findCheckbox().attributes('data-qa-selector')).toBe(props.dataQaSelector);
});
});
});
});
import { GlButton } from '@gitlab/ui';
import { within, fireEvent } from '@testing-library/dom';
import { shallowMount, mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import SignupForm from '~/pages/admin/application_settings/general/components/signup_form.vue';
import { mockData } from '../mock_data';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
describe('Signup Form', () => {
let wrapper;
let formSubmitSpy;
const mountComponent = ({ injectedProps = {}, mountFn = shallowMount, stubs = {} } = {}) => {
wrapper = extendedWrapper(
mountFn(SignupForm, {
provide: {
...mockData,
...injectedProps,
},
stubs,
}),
);
};
const queryByLabelText = (text) => within(wrapper.element).queryByLabelText(text);
const findForm = () => wrapper.findByTestId('form');
const findInputCsrf = () => findForm().find('[name="authenticity_token"]');
const findFormSubmitButton = () => findForm().find(GlButton);
const findDenyListRawRadio = () => queryByLabelText('Enter denylist manually');
const findDenyListFileRadio = () => queryByLabelText('Upload denylist file');
const findDenyListRawInputGroup = () => wrapper.findByTestId('domain-denylist-raw-input-group');
const findDenyListFileInputGroup = () => wrapper.findByTestId('domain-denylist-file-input-group');
afterEach(() => {
wrapper.destroy();
});
describe('form data', () => {
beforeEach(() => {
mountComponent();
});
it.each`
prop | propValue | elementSelector | formElementPassedDataType | formElementKey | expected
${'signupEnabled'} | ${mockData.signupEnabled} | ${'[name="application_setting[signup_enabled]"]'} | ${'prop'} | ${'value'} | ${mockData.signupEnabled}
${'requireAdminApprovalAfterUserSignup'} | ${mockData.requireAdminApprovalAfterUserSignup} | ${'[name="application_setting[require_admin_approval_after_user_signup]"]'} | ${'prop'} | ${'value'} | ${mockData.requireAdminApprovalAfterUserSignup}
${'sendUserConfirmationEmail'} | ${mockData.sendUserConfirmationEmail} | ${'[name="application_setting[send_user_confirmation_email]"]'} | ${'prop'} | ${'value'} | ${mockData.sendUserConfirmationEmail}
${'newUserSignupsCap'} | ${mockData.newUserSignupsCap} | ${'[name="application_setting[new_user_signups_cap]"]'} | ${'attribute'} | ${'value'} | ${mockData.newUserSignupsCap}
${'minimumPasswordLength'} | ${mockData.minimumPasswordLength} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'value'} | ${mockData.minimumPasswordLength}
${'minimumPasswordLengthMin'} | ${mockData.minimumPasswordLengthMin} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'min'} | ${mockData.minimumPasswordLengthMin}
${'minimumPasswordLengthMax'} | ${mockData.minimumPasswordLengthMax} | ${'[name="application_setting[minimum_password_length]"]'} | ${'attribute'} | ${'max'} | ${mockData.minimumPasswordLengthMax}
${'domainAllowlistRaw'} | ${mockData.domainAllowlistRaw} | ${'[name="application_setting[domain_allowlist_raw]"]'} | ${'value'} | ${'value'} | ${mockData.domainAllowlistRaw}
${'domainDenylistEnabled'} | ${mockData.domainDenylistEnabled} | ${'[name="application_setting[domain_denylist_enabled]"]'} | ${'prop'} | ${'value'} | ${mockData.domainDenylistEnabled}
${'denylistTypeRawSelected'} | ${mockData.denylistTypeRawSelected} | ${'[name="denylist_type"]'} | ${'attribute'} | ${'checked'} | ${'raw'}
${'domainDenylistRaw'} | ${mockData.domainDenylistRaw} | ${'[name="application_setting[domain_denylist_raw]"]'} | ${'value'} | ${'value'} | ${mockData.domainDenylistRaw}
${'emailRestrictionsEnabled'} | ${mockData.emailRestrictionsEnabled} | ${'[name="application_setting[email_restrictions_enabled]"]'} | ${'prop'} | ${'value'} | ${mockData.emailRestrictionsEnabled}
${'emailRestrictions'} | ${mockData.emailRestrictions} | ${'[name="application_setting[email_restrictions]"]'} | ${'value'} | ${'value'} | ${mockData.emailRestrictions}
${'afterSignUpText'} | ${mockData.afterSignUpText} | ${'[name="application_setting[after_sign_up_text]"]'} | ${'value'} | ${'value'} | ${mockData.afterSignUpText}
`(
'form element $elementSelector gets $expected value for $formElementKey $formElementPassedDataType when prop $prop is set to $propValue',
({ elementSelector, expected, formElementKey, formElementPassedDataType }) => {
const formElement = wrapper.find(elementSelector);
switch (formElementPassedDataType) {
case 'attribute':
expect(formElement.attributes(formElementKey)).toBe(expected);
break;
case 'prop':
expect(formElement.props(formElementKey)).toBe(expected);
break;
case 'value':
expect(formElement.element.value).toBe(expected);
break;
default:
expect(formElement.props(formElementKey)).toBe(expected);
break;
}
},
);
it('gets passed the path for action attribute', () => {
expect(findForm().attributes('action')).toBe(mockData.settingsPath);
});
it('gets passed the csrf token as a hidden input value', () => {
expect(findInputCsrf().attributes('type')).toBe('hidden');
expect(findInputCsrf().attributes('value')).toBe('mock-csrf-token');
});
});
describe('form submit', () => {
beforeEach(() => {
formSubmitSpy = jest.spyOn(HTMLFormElement.prototype, 'submit').mockImplementation();
mountComponent({ stubs: { GlButton } });
});
it('submits the form when the primary action is clicked', () => {
findFormSubmitButton().trigger('click');
expect(formSubmitSpy).toHaveBeenCalled();
});
});
describe('domain deny list', () => {
describe('when it is set to raw from props', () => {
beforeEach(() => {
mountComponent({ mountFn: mount });
});
it('has raw list selected', () => {
expect(findDenyListRawRadio().checked).toBe(true);
});
it('has file not selected', () => {
expect(findDenyListFileRadio().checked).toBe(false);
});
it('raw list input is displayed', () => {
expect(findDenyListRawInputGroup().exists()).toBe(true);
});
it('file input is not displayed', () => {
expect(findDenyListFileInputGroup().exists()).toBe(false);
});
describe('when user clicks on file radio', () => {
beforeEach(() => {
fireEvent.click(findDenyListFileRadio());
});
it('has raw list not selected', () => {
expect(findDenyListRawRadio().checked).toBe(false);
});
it('has file selected', () => {
expect(findDenyListFileRadio().checked).toBe(true);
});
it('raw list input is not displayed', () => {
expect(findDenyListRawInputGroup().exists()).toBe(false);
});
it('file input is displayed', () => {
expect(findDenyListFileInputGroup().exists()).toBe(true);
});
});
});
describe('when it is set to file from injected props', () => {
beforeEach(() => {
mountComponent({ mountFn: mount, injectedProps: { denylistTypeRawSelected: false } });
});
it('has raw list not selected', () => {
expect(findDenyListRawRadio().checked).toBe(false);
});
it('has file selected', () => {
expect(findDenyListFileRadio().checked).toBe(true);
});
it('raw list input is not displayed', () => {
expect(findDenyListRawInputGroup().exists()).toBe(false);
});
it('file input is displayed', () => {
expect(findDenyListFileInputGroup().exists()).toBe(true);
});
describe('when user clicks on raw list radio', () => {
beforeEach(() => {
fireEvent.click(findDenyListRawRadio());
});
it('has raw list selected', () => {
expect(findDenyListRawRadio().checked).toBe(true);
});
it('has file not selected', () => {
expect(findDenyListFileRadio().checked).toBe(false);
});
it('raw list input is displayed', () => {
expect(findDenyListRawInputGroup().exists()).toBe(true);
});
it('file input is not displayed', () => {
expect(findDenyListFileInputGroup().exists()).toBe(false);
});
});
});
});
});
export const rawMockData = {
host: 'path/to/host',
settingsPath: 'path/to/settings',
signupEnabled: 'true',
requireAdminApprovalAfterUserSignup: 'true',
sendUserConfirmationEmail: 'true',
minimumPasswordLength: '8',
minimumPasswordLengthMin: '3',
minimumPasswordLengthMax: '10',
minimumPasswordLengthHelpLink: 'help/link',
domainAllowlistRaw: 'domain1.com, domain2.com',
newUserSignupsCap: '8',
domainDenylistEnabled: 'true',
denylistTypeRawSelected: 'true',
domainDenylistRaw: 'domain2.com, domain3.com',
emailRestrictionsEnabled: 'true',
supportedSyntaxLinkUrl: '/supported/syntax/link',
emailRestrictions: 'user1@domain.com, user2@domain.com',
afterSignUpText: 'Congratulations on your successful sign-up!',
};
export const mockData = {
host: 'path/to/host',
settingsPath: 'path/to/settings',
signupEnabled: true,
requireAdminApprovalAfterUserSignup: true,
sendUserConfirmationEmail: true,
minimumPasswordLength: '8',
minimumPasswordLengthMin: '3',
minimumPasswordLengthMax: '10',
minimumPasswordLengthHelpLink: 'help/link',
domainAllowlistRaw: 'domain1.com, domain2.com',
newUserSignupsCap: '8',
domainDenylistEnabled: true,
denylistTypeRawSelected: true,
domainDenylistRaw: 'domain2.com, domain3.com',
emailRestrictionsEnabled: true,
supportedSyntaxLinkUrl: '/supported/syntax/link',
emailRestrictions: 'user1@domain.com, user2@domain.com',
afterSignUpText: 'Congratulations on your successful sign-up!',
};
export const setDataAttributes = (data, element) => {
Object.keys(data).forEach((key) => {
const value = data[key];
// attribute should be:
// - valueless if value is 'true'
// - absent if value is 'false'
switch (value) {
case false:
break;
case true:
element.dataset[`${key}`] = '';
break;
default:
element.dataset[`${key}`] = value;
break;
}
});
};
import { getParsedDataset } from '~/pages/admin/application_settings/utils';
import { rawMockData, mockData } from './mock_data';
describe('utils', () => {
describe('getParsedDataset', () => {
it('returns correct results', () => {
expect(
getParsedDataset({
dataset: rawMockData,
booleanAttributes: [
'signupEnabled',
'requireAdminApprovalAfterUserSignup',
'sendUserConfirmationEmail',
'domainDenylistEnabled',
'denylistTypeRawSelected',
'emailRestrictionsEnabled',
],
}),
).toEqual(mockData);
});
});
});
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