Commit 92c62ed1 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'xanf-prohibit-outer-forks' into 'master'

FE for "Allow group owners to disable projects forking outside of GMA-group

See merge request gitlab-org/gitlab!25246
parents 6f2abe2f 5f9e4d66
---
title: Added "Prohibit outer fork" setting for Group SAML
merge_request: 25246
author:
type: added
...@@ -4,53 +4,68 @@ import DirtyFormChecker from './dirty_form_checker'; ...@@ -4,53 +4,68 @@ import DirtyFormChecker from './dirty_form_checker';
import setupToggleButtons from '~/toggle_buttons'; import setupToggleButtons from '~/toggle_buttons';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
const toggleIfEnabled = (samlSetting, toggle) => { const CALLOUT_SELECTOR = '.js-callout';
if (samlSetting) toggle.click(); const HELPER_SELECTOR = '.js-helper-text';
}; const TOGGLE_SELECTOR = '.js-project-feature-toggle';
function getHelperText(el) {
return el.parentNode.querySelector(HELPER_SELECTOR);
}
function getCallout(el) {
return el.parentNode.querySelector(CALLOUT_SELECTOR);
}
function getToggle(el) {
return el.querySelector(TOGGLE_SELECTOR);
}
export default class SamlSettingsForm { export default class SamlSettingsForm {
constructor(formSelector) { constructor(formSelector) {
this.form = document.querySelector(formSelector); this.form = document.querySelector(formSelector);
this.samlEnabledToggleArea = this.form.querySelector('.js-group-saml-enabled-toggle-area'); this.settings = [
this.samlProviderEnabledInput = this.form.querySelector('.js-group-saml-enabled-input'); {
this.samlEnforcedSSOToggleArea = this.form.querySelector( name: 'group-saml',
'.js-group-saml-enforced-sso-toggle-area', el: this.form.querySelector('.js-group-saml-enabled-toggle-area'),
); },
this.samlEnforcedSSOInput = this.form.querySelector('.js-group-saml-enforced-sso-input'); {
this.samlEnforcedSSOToggle = this.form.querySelector('.js-group-saml-enforced-sso-toggle'); name: 'enforced-sso',
this.samlEnforcedSSOHelperText = this.form.querySelector( el: this.form.querySelector('.js-group-saml-enforced-sso-toggle-area'),
'.js-group-saml-enforced-sso-helper-text', dependsOn: 'group-saml',
); },
this.samlEnforcedGroupManagedAccountsToggleArea = this.form.querySelector( {
'.js-group-saml-enforced-group-managed-accounts-toggle-area', name: 'enforced-group-managed-accounts',
); el: this.form.querySelector('.js-group-saml-enforced-group-managed-accounts-toggle-area'),
this.samlEnforcedGroupManagedAccountsInput = this.form.querySelector( dependsOn: 'enforced-sso',
'.js-group-saml-enforced-group-managed-accounts-input', },
); {
this.samlEnforcedGroupManagedAccountsToggle = this.form.querySelector( name: 'prohibited-outer-forks',
'.js-group-saml-enforced-group-managed-accounts-toggle', el: this.form.querySelector('.js-group-saml-prohibited-outer-forks-toggle-area'),
); dependsOn: 'enforced-group-managed-accounts',
this.samlEnforcedGroupManagedAccountsHelperText = this.form.querySelector( },
'.js-group-saml-enforced-group-managed-accounts-helper-text', ]
); .filter(s => s.el)
this.samlEnforcedGroupManagedAccountsCallout = this.form.querySelector( .map(setting => ({
'.js-group-saml-enforced-group-managed-accounts-callout', ...setting,
); toggle: getToggle(setting.el),
helperText: getHelperText(setting.el),
callout: getCallout(setting.el),
input: setting.el.querySelector('input'),
}));
this.testButtonTooltipWrapper = this.form.querySelector('#js-saml-test-button'); this.testButtonTooltipWrapper = this.form.querySelector('#js-saml-test-button');
this.testButton = this.testButtonTooltipWrapper.querySelector('a'); this.testButton = this.testButtonTooltipWrapper.querySelector('a');
this.dirtyFormChecker = new DirtyFormChecker(formSelector, () => this.updateView()); this.dirtyFormChecker = new DirtyFormChecker(formSelector, () => this.updateView());
} }
getSettingValue(name) {
return this.settings.find(s => s.name === name).value;
}
init() { init() {
this.dirtyFormChecker.init(); this.dirtyFormChecker.init();
setupToggleButtons(this.form);
setupToggleButtons(this.samlEnabledToggleArea); $(this.form).on('trigger-change', () => this.onEnableToggle());
setupToggleButtons(this.samlEnforcedSSOToggleArea);
setupToggleButtons(this.samlEnforcedGroupManagedAccountsToggleArea);
$(this.samlProviderEnabledInput).on('trigger-change', () => this.onEnableToggle());
$(this.samlEnforcedSSOInput).on('trigger-change', () => this.onEnableToggle());
$(this.samlEnforcedGroupManagedAccountsInput).on('trigger-change', () => this.onEnableToggle());
this.updateSAMLSettings(); this.updateSAMLSettings();
this.updateView(); this.updateView();
} }
...@@ -61,11 +76,10 @@ export default class SamlSettingsForm { ...@@ -61,11 +76,10 @@ export default class SamlSettingsForm {
} }
updateSAMLSettings() { updateSAMLSettings() {
this.samlProviderEnabled = parseBoolean(this.samlProviderEnabledInput.value); this.settings = this.settings.map(setting => ({
this.samlEnforcedSSOEnabled = parseBoolean(this.samlEnforcedSSOInput.value); ...setting,
this.samlEnforcedGroupManagedAccountsEnabled = parseBoolean( value: parseBoolean(setting.el.querySelector('input').value),
this.samlEnforcedGroupManagedAccountsInput.value, }));
);
} }
testButtonTooltip() { testButtonTooltip() {
...@@ -80,47 +94,35 @@ export default class SamlSettingsForm { ...@@ -80,47 +94,35 @@ export default class SamlSettingsForm {
return __('Redirect to SAML provider to test configuration'); return __('Redirect to SAML provider to test configuration');
} }
updateSAMLToggles() { updateToggles() {
if (!this.samlProviderEnabled) { this.settings.forEach(setting => {
toggleIfEnabled(this.samlEnforcedSSOEnabled, this.samlEnforcedSSOToggle); const { helperText, callout, toggle } = setting;
toggleIfEnabled( if (setting.dependsOn) {
this.samlEnforcedGroupManagedAccountsEnabled, const dependencyToggleValue = this.getSettingValue(setting.dependsOn);
this.samlEnforcedGroupManagedAccountsToggle, if (helperText) {
); helperText.style.display = dependencyToggleValue ? 'none' : 'block';
} }
if (!this.samlEnforcedSSOEnabled) { if (!dependencyToggleValue && setting.value) {
toggleIfEnabled( setting.toggle.click();
this.samlEnforcedGroupManagedAccountsEnabled, }
this.samlEnforcedGroupManagedAccountsToggle, toggle.disabled = !dependencyToggleValue;
); }
}
if (callout) {
this.samlEnforcedSSOToggle.disabled = !this.samlProviderEnabled; callout.style.display = setting.value ? 'block' : 'none';
this.samlEnforcedGroupManagedAccountsToggle.disabled = }
!this.samlProviderEnabled || !this.samlEnforcedSSOEnabled; });
}
updateHelperTextAndCallouts() {
this.samlEnforcedSSOHelperText.style.display = this.samlProviderEnabled ? 'none' : 'block';
this.samlEnforcedGroupManagedAccountsHelperText.style.display = this.samlEnforcedSSOEnabled
? 'none'
: 'block';
this.samlEnforcedGroupManagedAccountsCallout.style.display = this
.samlEnforcedGroupManagedAccountsEnabled
? 'block'
: 'none';
} }
updateView() { updateView() {
if (this.samlProviderEnabled && !this.dirtyFormChecker.isDirty) { if (this.getSettingValue('group-saml') && !this.dirtyFormChecker.isDirty) {
this.testButton.removeAttribute('disabled'); this.testButton.removeAttribute('disabled');
} else { } else {
this.testButton.setAttribute('disabled', true); this.testButton.setAttribute('disabled', true);
} }
this.updateSAMLToggles(); this.updateToggles();
this.updateHelperTextAndCallouts();
// Update tooltip using wrapper so it works when input disabled // Update tooltip using wrapper so it works when input disabled
this.testButtonTooltipWrapper.setAttribute('title', this.testButtonTooltip()); this.testButtonTooltipWrapper.setAttribute('title', this.testButtonTooltip());
......
...@@ -9,23 +9,33 @@ ...@@ -9,23 +9,33 @@
- if Feature.enabled?(:enforced_sso, group) - if Feature.enabled?(:enforced_sso, group)
.form-group .form-group
%label.toggle-wrapper.mb-0.js-group-saml-enforced-sso-toggle-area %label.toggle-wrapper.mb-0.js-group-saml-enforced-sso-toggle-area
= render "shared/buttons/project_feature_toggle", is_checked: saml_provider.enabled?, label: s_("GroupSAML|Enforced SSO"), class_list: "js-project-feature-toggle js-group-saml-enforced-sso-toggle project-feature-toggle d-inline", data: { qa_selector: 'enforced_sso_toggle_button' } do = render "shared/buttons/project_feature_toggle", is_checked: saml_provider.enforced_sso?, label: s_("GroupSAML|Enforced SSO"), class_list: "js-project-feature-toggle js-group-saml-enforced-sso-toggle project-feature-toggle d-inline", data: { qa_selector: 'enforced_sso_toggle_button' } do
= f.hidden_field :enforced_sso, { class: 'js-group-saml-enforced-sso-input js-project-feature-toggle-input'} = f.hidden_field :enforced_sso, { class: 'js-group-saml-enforced-sso-input js-project-feature-toggle-input'}
%span.form-text.d-inline.font-weight-normal.align-text-bottom.ml-3= Feature.enabled?(:enforced_sso_requires_session, group) ? s_('GroupSAML|Enforce SSO-only authentication for this group.') : s_('GroupSAML|Enforce SSO-only membership for this group.') %span.form-text.d-inline.font-weight-normal.align-text-bottom.ml-3= Feature.enabled?(:enforced_sso_requires_session, group) ? s_('GroupSAML|Enforce SSO-only authentication for this group.') : s_('GroupSAML|Enforce SSO-only membership for this group.')
.form-text.text-muted.js-group-saml-enforced-sso-helper-text{ style: "display: #{'none' if saml_provider.enabled?} #{'block' unless saml_provider.enabled?}" } .form-text.text-muted.js-helper-text{ style: "display: #{'none' if saml_provider.enabled?} #{'block' unless saml_provider.enabled?}" }
%span %span
= s_('GroupSAML|To be able to enable enforced SSO, you first need to enable SAML authentication.') = s_('GroupSAML|To be able to enable enforced SSO, you first need to enable SAML authentication.')
- if Feature.enabled?(:group_managed_accounts, group) - if Feature.enabled?(:group_managed_accounts, group)
.form-group .form-group
%label.toggle-wrapper.mb-0.js-group-saml-enforced-group-managed-accounts-toggle-area %label.toggle-wrapper.mb-0.js-group-saml-enforced-group-managed-accounts-toggle-area
= render "shared/buttons/project_feature_toggle", is_checked: saml_provider.enabled?, label: s_("GroupSAML|Enforced SSO"), class_list: "js-project-feature-toggle js-group-saml-enforced-group-managed-accounts-toggle project-feature-toggle d-inline", data: { qa_selector: 'group_managed_accounts_toggle_button' } do = render "shared/buttons/project_feature_toggle", is_checked: saml_provider.enforced_group_managed_accounts?, label: s_("GroupSAML|Enforced SSO"), class_list: "js-project-feature-toggle js-group-saml-enforced-group-managed-accounts-toggle project-feature-toggle d-inline", data: { qa_selector: 'group_managed_accounts_toggle_button' } do
= f.hidden_field :enforced_group_managed_accounts, { class: 'js-group-saml-enforced-group-managed-accounts-input js-project-feature-toggle-input'} = f.hidden_field :enforced_group_managed_accounts, { class: 'js-group-saml-enforced-group-managed-accounts-input js-project-feature-toggle-input'}
%span.form-text.d-inline.font-weight-normal.align-text-bottom.ml-3= s_('GroupSAML|Enforce users to have dedicated group managed accounts for this group.') %span.form-text.d-inline.font-weight-normal.align-text-bottom.ml-3= s_('GroupSAML|Enforce users to have dedicated group managed accounts for this group.')
.form-text.text-muted.js-group-saml-enforced-group-managed-accounts-helper-text{ style: "display: #{'none' if saml_provider.enforced_sso?} #{'block' unless saml_provider.enforced_sso?}" } .form-text.text-muted.js-helper-text{ style: "display: #{'none' if saml_provider.enforced_sso?} #{'block' unless saml_provider.enforced_sso?}" }
%span %span
= s_('GroupSAML|To be able to enable group managed accounts, you first need to enable enforced SSO.') = s_('GroupSAML|To be able to enable group managed accounts, you first need to enable enforced SSO.')
.bs-callout.bs-callout-info.js-group-saml-enforced-group-managed-accounts-callout{ style: "display: #{'block' if saml_provider.enforced_sso?} #{'none' unless saml_provider.enforced_sso?}" } .bs-callout.bs-callout-info.js-callout{ style: "display: #{'block' if saml_provider.enforced_sso?} #{'none' unless saml_provider.enforced_sso?}" }
= s_('GroupSAML|With group managed accounts enabled, all the users without a group managed account will be excluded from the group.') = s_('GroupSAML|With group managed accounts enabled, all the users without a group managed account will be excluded from the group.')
.form-group
%label.toggle-wrapper.mb-0.js-group-saml-prohibited-outer-forks-toggle-area
= render "shared/buttons/project_feature_toggle", is_checked: saml_provider.prohibited_outer_forks?, label: s_("GroupSAML|Prohibit outer forks"), class_list: "js-project-feature-toggle js-group-saml-prohibited-outer-forks-toggle project-feature-toggle d-inline", data: { qa_selector: 'prohibited_outer_forks_toggle_button' } do
= f.hidden_field :prohibited_outer_forks, { class: 'js-group-saml-prohibited-outer-forks-input js-project-feature-toggle-input'}
%span.form-text.d-inline.font-weight-normal.align-text-bottom.ml-3= s_('GroupSAML|Prohibit outer forks for this group.')
.form-text.text-muted.js-helper-text{ style: "display: #{'none' if saml_provider.enforced_group_managed_accounts?} #{'block' unless saml_provider.enforced_group_managed_accounts?}" }
%span
= s_('GroupSAML|To be able to prohibit outer forks, you first need to enforce dedicate group managed accounts.')
.bs-callout.bs-callout-info.js-callout{ style: "display: #{'block' if saml_provider.enforced_group_managed_accounts?} #{'none' unless saml_provider.enforced_group_managed_accounts?}" }
= s_('GroupSAML|With prohibit outer forks flag enabled group members will be able to fork project only inside your group.')
.well-segment.borderless.mb-3.col-12.col-lg-9.p-0 .well-segment.borderless.mb-3.col-12.col-lg-9.p-0
= f.label :sso_url, class: 'label-bold' do = f.label :sso_url, class: 'label-bold' do
= s_('GroupSAML|Identity provider single sign on URL') = s_('GroupSAML|Identity provider single sign on URL')
......
...@@ -119,11 +119,10 @@ describe 'SAML provider settings' do ...@@ -119,11 +119,10 @@ describe 'SAML provider settings' do
context 'enforced_group_managed_accounts enabled', :js do context 'enforced_group_managed_accounts enabled', :js do
before do before do
create(:group_saml_identity, saml_provider: saml_provider, user: user) create(:group_saml_identity, saml_provider: saml_provider, user: user)
end
it 'updates the flag' do
stub_feature_flags(group_managed_accounts: true) stub_feature_flags(group_managed_accounts: true)
end
it 'updates the enforced_group_managed_accounts flag' do
visit group_saml_providers_path(group) visit group_saml_providers_path(group)
find('.js-group-saml-enforced-sso-toggle').click find('.js-group-saml-enforced-sso-toggle').click
...@@ -131,15 +130,26 @@ describe 'SAML provider settings' do ...@@ -131,15 +130,26 @@ describe 'SAML provider settings' do
expect { submit }.to change { saml_provider.reload.enforced_group_managed_accounts }.to(true) expect { submit }.to change { saml_provider.reload.enforced_group_managed_accounts }.to(true)
end end
it 'updates the prohibited_outer_forks flag' do
visit group_saml_providers_path(group)
find('.js-group-saml-enforced-sso-toggle').click
find('.js-group-saml-enforced-group-managed-accounts-toggle').click
find('.js-group-saml-prohibited-outer-forks-toggle').click
expect { submit }.to change { saml_provider.reload.prohibited_outer_forks }.to(true)
end
end end
context 'enforced_group_managed_accounts disabled' do context 'enforced_group_managed_accounts disabled' do
it 'does not update the flag' do it 'does not render toggles' do
stub_feature_flags(group_managed_accounts: false) stub_feature_flags(group_managed_accounts: false)
visit group_saml_providers_path(group) visit group_saml_providers_path(group)
expect(page).not_to have_selector('.js-group-saml-enforced-group-managed-accounts-toggle') expect(page).not_to have_selector('.js-group-saml-enforced-group-managed-accounts-toggle')
expect(page).not_to have_selector('.js-group-saml-prohibited-outer-forks-toggle')
end end
end end
end end
......
...@@ -2,7 +2,6 @@ import DirtyFormChecker from 'ee/saml_providers/dirty_form_checker'; ...@@ -2,7 +2,6 @@ import DirtyFormChecker from 'ee/saml_providers/dirty_form_checker';
describe('DirtyFormChecker', () => { describe('DirtyFormChecker', () => {
const FIXTURE = 'groups/saml_providers/show.html'; const FIXTURE = 'groups/saml_providers/show.html';
preloadFixtures(FIXTURE);
beforeEach(() => { beforeEach(() => {
loadFixtures(FIXTURE); loadFixtures(FIXTURE);
...@@ -34,7 +33,7 @@ describe('DirtyFormChecker', () => { ...@@ -34,7 +33,7 @@ describe('DirtyFormChecker', () => {
let onChangeCallback; let onChangeCallback;
beforeEach(() => { beforeEach(() => {
onChangeCallback = jasmine.createSpy('onChangeCallback'); onChangeCallback = jest.fn();
dirtyFormChecker = new DirtyFormChecker('#js-saml-settings-form', onChangeCallback); dirtyFormChecker = new DirtyFormChecker('#js-saml-settings-form', onChangeCallback);
}); });
......
import SamlSettingsForm from 'ee/saml_providers/saml_settings_form'; import SamlSettingsForm from 'ee/saml_providers/saml_settings_form';
import 'bootstrap';
describe('SamlSettingsForm', () => { describe('SamlSettingsForm', () => {
const FIXTURE = 'groups/saml_providers/show.html'; const FIXTURE = 'groups/saml_providers/show.html';
preloadFixtures(FIXTURE);
let samlSettingsForm;
beforeEach(() => { beforeEach(() => {
loadFixtures(FIXTURE); loadFixtures(FIXTURE);
samlSettingsForm = new SamlSettingsForm('#js-saml-settings-form');
samlSettingsForm.init();
}); });
describe('updateView', () => { describe('updateView', () => {
let samlSettingsForm;
beforeEach(() => {
samlSettingsForm = new SamlSettingsForm('#js-saml-settings-form');
samlSettingsForm.init();
});
it('disables Test button when form has changes', () => { it('disables Test button when form has changes', () => {
samlSettingsForm.dirtyFormChecker.isDirty = true; samlSettingsForm.dirtyFormChecker.isDirty = true;
...@@ -35,7 +31,7 @@ describe('SamlSettingsForm', () => { ...@@ -35,7 +31,7 @@ describe('SamlSettingsForm', () => {
}); });
it('keeps Test button disabled when SAML disabled for the group', () => { it('keeps Test button disabled when SAML disabled for the group', () => {
samlSettingsForm.samlProviderEnabled = false; samlSettingsForm.settings.find(s => s.name === 'group-saml').value = false;
samlSettingsForm.testButton.setAttribute('disabled', true); samlSettingsForm.testButton.setAttribute('disabled', true);
samlSettingsForm.updateView(); samlSettingsForm.updateView();
...@@ -43,4 +39,54 @@ describe('SamlSettingsForm', () => { ...@@ -43,4 +39,54 @@ describe('SamlSettingsForm', () => {
expect(samlSettingsForm.testButton.hasAttribute('disabled')).toBe(true); expect(samlSettingsForm.testButton.hasAttribute('disabled')).toBe(true);
}); });
}); });
it('correctly turns off dependent toggle', () => {
samlSettingsForm.settings.forEach(s => {
const { input } = s;
input.value = true;
});
const enforcedGroupManagedAccountSetting = samlSettingsForm.settings.find(
s => s.name === 'enforced-group-managed-accounts',
);
const prohibitForksSetting = samlSettingsForm.settings.find(
s => s.name === 'prohibited-outer-forks',
);
samlSettingsForm.updateSAMLSettings();
samlSettingsForm.updateView();
expect(prohibitForksSetting.toggle.hasAttribute('disabled')).toBe(false);
enforcedGroupManagedAccountSetting.input.value = false;
samlSettingsForm.updateSAMLSettings();
samlSettingsForm.updateView();
expect(prohibitForksSetting.toggle.hasAttribute('disabled')).toBe(true);
expect(prohibitForksSetting.value).toBe(false);
});
it('correctly turns off multiple dependent toggles', () => {
samlSettingsForm.settings.forEach(s => {
const { input } = s;
input.value = true;
});
const [groupSamlSetting, ...otherSettings] = samlSettingsForm.settings;
samlSettingsForm.updateSAMLSettings();
samlSettingsForm.updateView();
expect(samlSettingsForm.settings.map(s => s.value)).not.toContain(false);
expect(samlSettingsForm.settings.map(s => s.toggle.hasAttribute('disabled'))).not.toContain(
true,
);
groupSamlSetting.input.value = false;
samlSettingsForm.updateSAMLSettings();
samlSettingsForm.updateView();
return new Promise(window.requestAnimationFrame).then(() => {
expect(samlSettingsForm.settings.map(s => s.value)).not.toContain(true);
expect(otherSettings.map(s => s.toggle.hasAttribute('disabled'))).not.toContain(false);
});
});
}); });
...@@ -9678,6 +9678,12 @@ msgstr "" ...@@ -9678,6 +9678,12 @@ msgstr ""
msgid "GroupSAML|NameID Format" msgid "GroupSAML|NameID Format"
msgstr "" msgstr ""
msgid "GroupSAML|Prohibit outer forks"
msgstr ""
msgid "GroupSAML|Prohibit outer forks for this group."
msgstr ""
msgid "GroupSAML|SAML Response Output" msgid "GroupSAML|SAML Response Output"
msgstr "" msgstr ""
...@@ -9708,6 +9714,9 @@ msgstr "" ...@@ -9708,6 +9714,9 @@ msgstr ""
msgid "GroupSAML|To be able to enable group managed accounts, you first need to enable enforced SSO." msgid "GroupSAML|To be able to enable group managed accounts, you first need to enable enforced SSO."
msgstr "" msgstr ""
msgid "GroupSAML|To be able to prohibit outer forks, you first need to enforce dedicate group managed accounts."
msgstr ""
msgid "GroupSAML|Toggle SAML authentication" msgid "GroupSAML|Toggle SAML authentication"
msgstr "" msgstr ""
...@@ -9717,6 +9726,9 @@ msgstr "" ...@@ -9717,6 +9726,9 @@ msgstr ""
msgid "GroupSAML|With group managed accounts enabled, all the users without a group managed account will be excluded from the group." msgid "GroupSAML|With group managed accounts enabled, all the users without a group managed account will be excluded from the group."
msgstr "" msgstr ""
msgid "GroupSAML|With prohibit outer forks flag enabled group members will be able to fork project only inside your group."
msgstr ""
msgid "GroupSAML|Your SCIM token" msgid "GroupSAML|Your SCIM token"
msgstr "" msgstr ""
......
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