Commit 5f9e4d66 authored by Illya Klymov's avatar Illya Klymov

Add "Prohibit outer forks" setting for Group SAML

- Adds additional toggle to prohibit outer forks
- Updates test to respect new changes
parent b1fe7c90
---
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';
import setupToggleButtons from '~/toggle_buttons';
import { parseBoolean } from '~/lib/utils/common_utils';
const toggleIfEnabled = (samlSetting, toggle) => {
if (samlSetting) toggle.click();
};
const CALLOUT_SELECTOR = '.js-callout';
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 {
constructor(formSelector) {
this.form = document.querySelector(formSelector);
this.samlEnabledToggleArea = this.form.querySelector('.js-group-saml-enabled-toggle-area');
this.samlProviderEnabledInput = this.form.querySelector('.js-group-saml-enabled-input');
this.samlEnforcedSSOToggleArea = this.form.querySelector(
'.js-group-saml-enforced-sso-toggle-area',
);
this.samlEnforcedSSOInput = this.form.querySelector('.js-group-saml-enforced-sso-input');
this.samlEnforcedSSOToggle = this.form.querySelector('.js-group-saml-enforced-sso-toggle');
this.samlEnforcedSSOHelperText = this.form.querySelector(
'.js-group-saml-enforced-sso-helper-text',
);
this.samlEnforcedGroupManagedAccountsToggleArea = this.form.querySelector(
'.js-group-saml-enforced-group-managed-accounts-toggle-area',
);
this.samlEnforcedGroupManagedAccountsInput = this.form.querySelector(
'.js-group-saml-enforced-group-managed-accounts-input',
);
this.samlEnforcedGroupManagedAccountsToggle = this.form.querySelector(
'.js-group-saml-enforced-group-managed-accounts-toggle',
);
this.samlEnforcedGroupManagedAccountsHelperText = this.form.querySelector(
'.js-group-saml-enforced-group-managed-accounts-helper-text',
);
this.samlEnforcedGroupManagedAccountsCallout = this.form.querySelector(
'.js-group-saml-enforced-group-managed-accounts-callout',
);
this.settings = [
{
name: 'group-saml',
el: this.form.querySelector('.js-group-saml-enabled-toggle-area'),
},
{
name: 'enforced-sso',
el: this.form.querySelector('.js-group-saml-enforced-sso-toggle-area'),
dependsOn: 'group-saml',
},
{
name: 'enforced-group-managed-accounts',
el: this.form.querySelector('.js-group-saml-enforced-group-managed-accounts-toggle-area'),
dependsOn: 'enforced-sso',
},
{
name: 'prohibited-outer-forks',
el: this.form.querySelector('.js-group-saml-prohibited-outer-forks-toggle-area'),
dependsOn: 'enforced-group-managed-accounts',
},
]
.filter(s => s.el)
.map(setting => ({
...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.testButton = this.testButtonTooltipWrapper.querySelector('a');
this.dirtyFormChecker = new DirtyFormChecker(formSelector, () => this.updateView());
}
getSettingValue(name) {
return this.settings.find(s => s.name === name).value;
}
init() {
this.dirtyFormChecker.init();
setupToggleButtons(this.samlEnabledToggleArea);
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());
setupToggleButtons(this.form);
$(this.form).on('trigger-change', () => this.onEnableToggle());
this.updateSAMLSettings();
this.updateView();
}
......@@ -61,11 +76,10 @@ export default class SamlSettingsForm {
}
updateSAMLSettings() {
this.samlProviderEnabled = parseBoolean(this.samlProviderEnabledInput.value);
this.samlEnforcedSSOEnabled = parseBoolean(this.samlEnforcedSSOInput.value);
this.samlEnforcedGroupManagedAccountsEnabled = parseBoolean(
this.samlEnforcedGroupManagedAccountsInput.value,
);
this.settings = this.settings.map(setting => ({
...setting,
value: parseBoolean(setting.el.querySelector('input').value),
}));
}
testButtonTooltip() {
......@@ -80,47 +94,35 @@ export default class SamlSettingsForm {
return __('Redirect to SAML provider to test configuration');
}
updateSAMLToggles() {
if (!this.samlProviderEnabled) {
toggleIfEnabled(this.samlEnforcedSSOEnabled, this.samlEnforcedSSOToggle);
toggleIfEnabled(
this.samlEnforcedGroupManagedAccountsEnabled,
this.samlEnforcedGroupManagedAccountsToggle,
);
updateToggles() {
this.settings.forEach(setting => {
const { helperText, callout, toggle } = setting;
if (setting.dependsOn) {
const dependencyToggleValue = this.getSettingValue(setting.dependsOn);
if (helperText) {
helperText.style.display = dependencyToggleValue ? 'none' : 'block';
}
if (!this.samlEnforcedSSOEnabled) {
toggleIfEnabled(
this.samlEnforcedGroupManagedAccountsEnabled,
this.samlEnforcedGroupManagedAccountsToggle,
);
if (!dependencyToggleValue && setting.value) {
setting.toggle.click();
}
this.samlEnforcedSSOToggle.disabled = !this.samlProviderEnabled;
this.samlEnforcedGroupManagedAccountsToggle.disabled =
!this.samlProviderEnabled || !this.samlEnforcedSSOEnabled;
toggle.disabled = !dependencyToggleValue;
}
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';
if (callout) {
callout.style.display = setting.value ? 'block' : 'none';
}
});
}
updateView() {
if (this.samlProviderEnabled && !this.dirtyFormChecker.isDirty) {
if (this.getSettingValue('group-saml') && !this.dirtyFormChecker.isDirty) {
this.testButton.removeAttribute('disabled');
} else {
this.testButton.setAttribute('disabled', true);
}
this.updateSAMLToggles();
this.updateHelperTextAndCallouts();
this.updateToggles();
// Update tooltip using wrapper so it works when input disabled
this.testButtonTooltipWrapper.setAttribute('title', this.testButtonTooltip());
......
......@@ -9,23 +9,33 @@
- if Feature.enabled?(:enforced_sso, group)
.form-group
%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'}
%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
= s_('GroupSAML|To be able to enable enforced SSO, you first need to enable SAML authentication.')
- if Feature.enabled?(:group_managed_accounts, group)
.form-group
%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'}
%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
= 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.')
.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
= f.label :sso_url, class: 'label-bold' do
= s_('GroupSAML|Identity provider single sign on URL')
......
......@@ -119,11 +119,10 @@ describe 'SAML provider settings' do
context 'enforced_group_managed_accounts enabled', :js do
before do
create(:group_saml_identity, saml_provider: saml_provider, user: user)
end
it 'updates the flag' do
stub_feature_flags(group_managed_accounts: true)
end
it 'updates the enforced_group_managed_accounts flag' do
visit group_saml_providers_path(group)
find('.js-group-saml-enforced-sso-toggle').click
......@@ -131,15 +130,26 @@ describe 'SAML provider settings' do
expect { submit }.to change { saml_provider.reload.enforced_group_managed_accounts }.to(true)
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
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)
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-prohibited-outer-forks-toggle')
end
end
end
......
......@@ -2,7 +2,6 @@ import DirtyFormChecker from 'ee/saml_providers/dirty_form_checker';
describe('DirtyFormChecker', () => {
const FIXTURE = 'groups/saml_providers/show.html';
preloadFixtures(FIXTURE);
beforeEach(() => {
loadFixtures(FIXTURE);
......@@ -34,7 +33,7 @@ describe('DirtyFormChecker', () => {
let onChangeCallback;
beforeEach(() => {
onChangeCallback = jasmine.createSpy('onChangeCallback');
onChangeCallback = jest.fn();
dirtyFormChecker = new DirtyFormChecker('#js-saml-settings-form', onChangeCallback);
});
......
import SamlSettingsForm from 'ee/saml_providers/saml_settings_form';
import 'bootstrap';
describe('SamlSettingsForm', () => {
const FIXTURE = 'groups/saml_providers/show.html';
preloadFixtures(FIXTURE);
beforeEach(() => {
loadFixtures(FIXTURE);
});
describe('updateView', () => {
let samlSettingsForm;
beforeEach(() => {
loadFixtures(FIXTURE);
samlSettingsForm = new SamlSettingsForm('#js-saml-settings-form');
samlSettingsForm.init();
});
describe('updateView', () => {
it('disables Test button when form has changes', () => {
samlSettingsForm.dirtyFormChecker.isDirty = true;
......@@ -35,7 +31,7 @@ describe('SamlSettingsForm', () => {
});
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.updateView();
......@@ -43,4 +39,54 @@ describe('SamlSettingsForm', () => {
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 ""
msgid "GroupSAML|NameID Format"
msgstr ""
msgid "GroupSAML|Prohibit outer forks"
msgstr ""
msgid "GroupSAML|Prohibit outer forks for this group."
msgstr ""
msgid "GroupSAML|SAML Response Output"
msgstr ""
......@@ -9708,6 +9714,9 @@ msgstr ""
msgid "GroupSAML|To be able to enable group managed accounts, you first need to enable enforced SSO."
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"
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."
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"
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