Commit ce0580d3 authored by Brandon Labuschagne's avatar Brandon Labuschagne Committed by Fatih Acet

Add SCIM Token section to SAML SSO Settings

Remove the grey background on some page elements
Add generate SCIM token button
parent f3724b8f
import initSAML from './shared/init_saml';
document.addEventListener('DOMContentLoaded', initSAML);
import SamlSettingsForm from 'ee/saml_providers/saml_settings_form';
import SCIMTokenToggleArea from 'ee/saml_providers/scim_token_toggle_area';
export default function initSAML() {
const groupPath = document.querySelector('#issuer').value;
// eslint-disable-next-line no-new
new SCIMTokenToggleArea(
'.js-generate-scim-token-container',
'.js-scim-token-container',
groupPath,
);
new SamlSettingsForm('#js-saml-settings-form').init();
}
import SamlSettingsForm from 'ee/saml_providers/saml_settings_form';
document.addEventListener('DOMContentLoaded', () => {
new SamlSettingsForm('#js-saml-settings-form').init();
});
import axios from '~/lib/utils/axios_utils';
export default class SCIMTokenService {
constructor(groupPath) {
this.axios = axios.create({
baseURL: groupPath,
});
}
generateNewSCIMToken() {
return this.axios.post('/-/scim_oauth');
}
}
import { __ } from '~/locale';
import createFlash from '~/flash';
import SCIMTokenService from './scim_token_service';
export default class SCIMTokenToggleArea {
constructor(generateSelector, formSelector, groupPath) {
this.generateContainer = document.querySelector(generateSelector);
this.formContainer = document.querySelector(formSelector);
this.scimLoadingSpinner = document.querySelector('.js-scim-loading-container');
this.generateButton = this.generateContainer.querySelector('.js-generate-scim-token');
this.resetButton = this.formContainer.querySelector('.js-reset-scim-token');
this.scimTokenInput = this.formContainer.querySelector('#scim_token');
this.scimEndpointUrl = this.formContainer.querySelector('#scim_endpoint_url');
this.generateButton.addEventListener('click', () => this.generateSCIMToken());
this.resetButton.addEventListener('click', () => this.resetSCIMToken());
this.service = new SCIMTokenService(groupPath);
}
setSCIMTokenValue(value) {
this.scimTokenInput.value = value;
}
setSCIMEndpointURL(value) {
this.scimEndpointUrl.value = value;
}
toggleSCIMTokenHelperText() {
this.formContainer.querySelector('.input-group-append').classList.toggle('d-none');
this.formContainer
.querySelector('.js-scim-token-helper-text span:first-of-type')
.classList.toggle('d-none');
this.formContainer
.querySelector('.js-scim-token-helper-text span:last-of-type')
.classList.toggle('d-none');
}
// eslint-disable-next-line class-methods-use-this
toggleFormVisibility(form) {
form.classList.toggle('d-none');
}
setSCIMTokenFormTitle(title) {
this.formContainer.querySelector('label:first-of-type').innerHTML = title;
}
toggleLoading() {
this.scimLoadingSpinner.classList.toggle('d-none');
}
setTokenAndToggleSCIMForm(data) {
this.setSCIMTokenValue(data.scim_token);
this.setSCIMEndpointURL(data.scim_api_url);
this.setSCIMTokenFormTitle(__('Your new SCIM token'));
this.toggleSCIMTokenHelperText();
this.toggleLoading();
this.toggleFormVisibility(this.formContainer);
}
fetchNewToken() {
return this.service.generateNewSCIMToken();
}
handleTokenGeneration(container) {
this.toggleFormVisibility(container);
this.toggleLoading();
return this.fetchNewToken()
.then(response => {
this.setTokenAndToggleSCIMForm(response.data);
})
.catch(error => {
createFlash(error);
this.toggleLoading();
this.toggleFormVisibility(container);
throw error;
});
}
generateSCIMToken() {
return this.handleTokenGeneration(this.generateContainer);
}
resetSCIMToken() {
if (
// eslint-disable-next-line no-alert
window.confirm(
__(
'Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated.',
),
)
) {
return this.handleTokenGeneration(this.formContainer);
}
return Promise.resolve();
}
}
......@@ -13,37 +13,35 @@
.form-text.d-inline.ml-3.align-text-bottom= s_('GroupSAML|Enable SAML authentication for this group')
- if Feature.enabled?(:enforced_sso, group)
.form-group.row
= f.label :enforced_sso, _("Enforced SSO"), class: 'col-form-label col-sm-2'
.col-sm-10
= f.label :enforced_sso, s_('GroupSAML|Enforced SSO'), class: 'col-form-label col-sm-2'
.col-sm-10.pt-1
.form-check
= f.check_box :enforced_sso, class: 'form-check-input'
= f.label :enforced_sso, class: 'form-check-label' do
= _("Enforce SSO-only authentication for this group")
= s_('GroupSAML|Enforce SSO-only authentication for this group')
- if Feature.enabled?(:group_managed_accounts, group)
.form-group.row
= f.label :enforced_group_managed_accounts, _("Group managed accounts"), class: 'col-form-label col-sm-2'
.col-sm-10
= f.label :enforced_group_managed_accounts, s_('GroupSAML|Group managed accounts'), class: 'col-form-label col-sm-2'
.col-sm-10.pt-1
.form-check
= f.check_box :enforced_group_managed_accounts, class: 'form-check-input'
= f.label :enforced_group_managed_accounts, class: 'form-check-label' do
= _("Enforce users to have dedicated group managed accounts for this group")
.form-group.row
= f.label :sso_url, class: 'col-form-label col-sm-2' do
= _("Identity provider single sign on URL")
.col-sm-10
= f.text_field :sso_url, placeholder: 'e.g. https://example.com/adfs/ls', class: 'form-control qa-identity-provider-sso-field'
.form-text.text-muted
= _('Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called "SSO Service Location", "SAML Token Issuance Endpoint", or "SAML 2.0/W-Federation URL".')
= s_('GroupSAML|Enforce users to have dedicated group managed accounts for this 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')
= f.text_field :sso_url, placeholder: 'e.g. https://example.com/adfs/ls', class: 'form-control qa-identity-provider-sso-field'
.form-text.text-muted
= s_('GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called "SSO Service Location", "SAML Token Issuance Endpoint", or "SAML 2.0/W-Federation URL".')
.form-group.row
= f.label :certificate_fingerprint, class: 'col-form-label col-sm-2' do
= _("Certificate fingerprint")
.col-sm-10
= f.text_field :certificate_fingerprint, placeholder: 'e.g. 0a:1b:2c:3d:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff', class: 'form-control qa-certificate-fingerprint-field'
.form-text.text-muted
= _('SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called "Thumbprint".')
.well-segment.borderless.mb-3.col-12.col-lg-9.p-0
= f.label :certificate_fingerprint, class: 'label-bold' do
= s_('GroupSAML|Certificate fingerprint')
= f.text_field :certificate_fingerprint, placeholder: 'e.g. 0a:1b:2c:3d:00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff', class: 'form-control qa-certificate-fingerprint-field'
.form-text.text-muted
= s_('GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called "Thumbprint".')
.form-actions
.mt-3
= f.submit _("Save changes"), class: 'btn btn-success qa-save-changes-button'
#js-saml-test-button.has-tooltip.pull-right
= render 'test_button', saml_provider: @saml_provider
%section.saml-settings.info-well.append-bottom-20
%section.saml-settings.prepend-top-10.append-bottom-20
.well-segment
%p= _("To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:")
%ol
......@@ -15,11 +15,11 @@
= (_("Fill in the fields below, turn on <strong>%{enable_label}</strong>, and press <strong>%{save_changes}</strong>") % { enable_label: _('Enable SAML authentication for this group'), save_changes: _('Save changes') }).html_safe
%li
= (_("Share the <strong>%{sso_label}</strong> with members so they can sign in to your group through your identity provider") % { sso_label: _('GitLab single sign on URL') }).html_safe
.well-segment.borderless
.well-segment.borderless.mb-3
= render 'info_row', field: :assertion_consumer_service_url, label_text: _('Assertion consumer service URL')
.form-text.text-muted= _('Also called "Relying party service URL" or "Reply URL"')
.well-segment.borderless
= render 'info_row', field: :issuer, label_text: 'Identifier'
= render 'info_row', field: :issuer, label_text: _('Identifier')
.form-text.text-muted= _('Also called "Issuer" or "Relying party trust identifier"')
- if group_saml_metadata_enabled?(@group)
.well-segment.borderless
......
- value = local_assigns[:value] || @saml_provider.send(field)
= label_tag field, label_text
= label_tag field, label_text, class: 'label-bold'
.clipboard-input-group.input-group
= text_field_tag field, value, class: "js-select-on-focus form-control", readonly: true, aria: { label: label_text }
= text_field_tag field, value, class: "js-select-on-focus form-control bg-white", readonly: true, aria: { label: label_text }
.input-group-append
= clipboard_button(target: "##{field}", title: _("Copy URL to clipboard"), class: "btn-default btn-gray")
- value = local_assigns[:value]
= label_tag field, label_text, class: 'label-bold'
.clipboard-input-group.input-group
= text_field_tag field, value, class: "js-select-on-focus form-control", readonly: true, aria: { label: label_text }
.input-group-append{ class: "#{ 'd-none' unless show_clipboard }" }
= clipboard_button(target: "##{field}", title: _("Copy URL to clipboard"), class: "btn-default btn-gray")
- scim_token_not_present = @scim_token_url.nil?
.prepend-top-default.js-generate-scim-token-container{ class: "#{ 'd-none' unless scim_token_not_present}" }
%p= s_('GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management.')
%button.btn.btn_default.btn-white.js-generate-scim-token{ type: 'button' }
= s_('GroupSAML|Generate a SCIM token')
.prepend-top-default.js-scim-token-container{ class: "#{ 'd-none' if scim_token_not_present}" }
.well-segment.borderless.mb-3.col-12.col-lg-9.p-0
= render 'scim_row', value: '********************', field: 'scim_token', label_text: s_('GroupSAML|Your SCIM token'), show_clipboard: false
.form-text.text-muted.js-scim-token-helper-text
%span
= s_('GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to ')
%button.btn.btn-default.btn-link.d-inline.align-baseline.js-reset-scim-token{ type: 'button' }
= _('reset it.')
%span.d-none
= s_("GroupSAML|Make sure you save this token — you won't be able to access it again.")
.well-segment.borderless.col-12.col-lg-9.p-0
= render 'scim_row', value: @scim_token_url, field: 'scim_endpoint_url', label_text: s_('GroupSAML|SCIM API endpoint URL'), show_clipboard: true
.prepend-top-default.text-center.js-scim-loading-container.d-none
.spinner
- page_title _('SAML Single Sign On Settings')
- page_title s_('GroupSAML|SAML Single Sign On Settings')
%section.row.prepend-top-default
.col-lg-3.append-bottom-default
%h4.page-title
= _("SAML Single Sign On")
= s_("GroupSAML|SAML Single Sign On")
%p
= _("Manage your group’s membership while adding another level of security with SAML.")
= s_("GroupSAML|Manage your group’s membership while adding another level of security with SAML.")
= link_to help_page_path('user/group/saml_sso/index'), target: '_blank' do
= _("Learn more")
= icon('external-link')
.col-lg-9
= render 'info'
= render 'form', group: @group, saml_provider: @saml_provider
- if Feature.enabled?(:group_scim, @group)
%section.row.border-top.mt-4
.col-lg-3.append-bottom-default
%h4.page-title
= s_('GroupSAML|SCIM Token')
.col-lg-9
= render 'scim_token'
---
title: Add SCIM Token section to SAML SSO Settings
merge_request: 9619
author:
type: added
# frozen_string_literal: true
require 'spec_helper'
describe 'SCIM Token handling', :js do
let(:user) { create(:user) }
let(:group) { create(:group) }
before do
group.add_owner(user)
stub_licensed_features(group_saml: true)
end
describe 'group has no existing scim token' do
before do
sign_in(user)
visit group_saml_providers_path(group)
end
it 'displays generate token form' do
expect(page).to have_selector('.js-generate-scim-token-container', visible: true)
expect(page).to have_selector('.js-scim-token-container', visible: false)
page.within '.js-generate-scim-token-container' do
expect(page).to have_content('Generate a SCIM token to set up your System for Cross-Domain Identity Management.')
expect(page).to have_button('Generate a SCIM token')
end
end
end
describe 'group has existing scim token' do
let!(:scim_token) { create(:scim_oauth_access_token, group: group) }
before do
sign_in(user)
visit group_saml_providers_path(group)
end
it 'displays the scim form with an obfuscated token' do
expect(page).to have_selector('.js-generate-scim-token-container', visible: false)
expect(page).to have_selector('.js-scim-token-container', visible: true)
page.within '.js-scim-token-container' do
expect(page).to have_button('reset it.')
expect(page.find('#scim_token').value).to eq('********************')
expect(page.find('#scim_endpoint_url').value).to eq(scim_token.as_entity_json[:scim_api_url])
end
end
end
end
import SCIMTokenToggleArea from 'ee/saml_providers/scim_token_toggle_area';
import { TEST_HOST } from 'spec/test_constants';
const mockData = {
data: {
scim_token: 'foobar',
scim_api_url: `${TEST_HOST}/scim/api`,
},
};
describe('SCIMTokenToggleArea', () => {
const FIXTURE = 'groups/saml_providers/show.html';
let scimTokenToggleArea;
let generateNewSCIMToken;
preloadFixtures(FIXTURE);
beforeEach(() => {
loadFixtures(FIXTURE);
generateNewSCIMToken = jasmine
.createSpy('generateNewSCIMToken')
.and.callFake(() => Promise.resolve(mockData));
spyOnDependency(SCIMTokenToggleArea, 'SCIMTokenService').and.returnValue({
generateNewSCIMToken,
});
scimTokenToggleArea = new SCIMTokenToggleArea(
'.js-generate-scim-token-container',
'.js-scim-token-container',
);
});
describe('constructor', () => {
it('receives a form which displays an obfuscated token', () => {
expect(scimTokenToggleArea.scimTokenInput.value).toBe('********************');
});
it('displays the generate token form and hides the scim token form', () => {
expect(scimTokenToggleArea.generateContainer).not.toHaveClass('d-none');
expect(scimTokenToggleArea.formContainer).toHaveClass('d-none');
});
});
describe('generateSCIMToken', () => {
it('toggles the generate and scim token forms', done => {
scimTokenToggleArea
.generateSCIMToken()
.then(() => {
expect(scimTokenToggleArea.generateContainer).toHaveClass('d-none');
expect(scimTokenToggleArea.formContainer).not.toHaveClass('d-none');
})
.then(done)
.catch(done.fail);
});
it('populates the scim form with the token data', done => {
scimTokenToggleArea
.generateSCIMToken()
.then(() => {
expect(scimTokenToggleArea.scimTokenInput.value).toBe(mockData.data.scim_token);
expect(scimTokenToggleArea.scimEndpointUrl.value).toBe(mockData.data.scim_api_url);
})
.then(done)
.catch(done.fail);
});
});
describe('resetSCIMToken', () => {
it('does not trigger token generation when the confirm is canceled', () => {
spyOn(window, 'confirm').and.returnValue(false);
scimTokenToggleArea.resetSCIMToken();
expect(generateNewSCIMToken).not.toHaveBeenCalled();
});
it('populates the scim form with the token data if the confirm is accepted', done => {
spyOn(window, 'confirm').and.returnValue(true);
scimTokenToggleArea
.resetSCIMToken()
.then(() => {
expect(scimTokenToggleArea.scimTokenInput.value).toBe(mockData.data.scim_token);
expect(scimTokenToggleArea.scimEndpointUrl.value).toBe(mockData.data.scim_api_url);
})
.then(done)
.catch(done.fail);
});
});
});
......@@ -1245,6 +1245,9 @@ msgstr ""
msgid "Are you sure you want to reset registration token?"
msgstr ""
msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
msgstr ""
msgid "Are you sure you want to reset the health check token?"
msgstr ""
......@@ -1920,9 +1923,6 @@ msgstr ""
msgid "Certificate (PEM)"
msgstr ""
msgid "Certificate fingerprint"
msgstr ""
msgid "Change Weight"
msgstr ""
......@@ -3880,15 +3880,6 @@ msgstr ""
msgid "Ends at (UTC)"
msgstr ""
msgid "Enforce SSO-only authentication for this group"
msgstr ""
msgid "Enforce users to have dedicated group managed accounts for this group"
msgstr ""
msgid "Enforced SSO"
msgstr ""
msgid "Enter at least three characters to search"
msgstr ""
......@@ -5331,9 +5322,6 @@ msgstr ""
msgid "Group maintainers can register group runners in the %{link}"
msgstr ""
msgid "Group managed accounts"
msgstr ""
msgid "Group name"
msgstr ""
......@@ -5373,12 +5361,66 @@ msgstr ""
msgid "GroupRoadmap|Until %{dateWord}"
msgstr ""
msgid "GroupSAML|Certificate fingerprint"
msgstr ""
msgid "GroupSAML|Enable SAML authentication for this group"
msgstr ""
msgid "GroupSAML|Enforce SSO-only authentication for this group"
msgstr ""
msgid "GroupSAML|Enforce users to have dedicated group managed accounts for this group"
msgstr ""
msgid "GroupSAML|Enforced SSO"
msgstr ""
msgid "GroupSAML|Generate a SCIM token"
msgstr ""
msgid "GroupSAML|Generate a SCIM token to set up your System for Cross-Domain Identity Management."
msgstr ""
msgid "GroupSAML|Group managed accounts"
msgstr ""
msgid "GroupSAML|Identity provider single sign on URL"
msgstr ""
msgid "GroupSAML|Make sure you save this token — you won't be able to access it again."
msgstr ""
msgid "GroupSAML|Manage your group’s membership while adding another level of security with SAML."
msgstr ""
msgid "GroupSAML|Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
msgstr ""
msgid "GroupSAML|SAML Single Sign On"
msgstr ""
msgid "GroupSAML|SAML Single Sign On Settings"
msgstr ""
msgid "GroupSAML|SCIM API endpoint URL"
msgstr ""
msgid "GroupSAML|SCIM Token"
msgstr ""
msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
msgstr ""
msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
msgstr ""
msgid "GroupSAML|Toggle SAML authentication"
msgstr ""
msgid "GroupSAML|Your SCIM token"
msgstr ""
msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
msgstr ""
......@@ -5642,9 +5684,6 @@ msgstr ""
msgid "Identities"
msgstr ""
msgid "Identity provider single sign on URL"
msgstr ""
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
......@@ -6524,9 +6563,6 @@ msgstr ""
msgid "Manage two-factor authentication"
msgstr ""
msgid "Manage your group’s membership while adding another level of security with SAML."
msgstr ""
msgid "Manifest"
msgstr ""
......@@ -6617,9 +6653,6 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
msgstr ""
msgid "Merge Request"
msgstr ""
......@@ -9167,18 +9200,9 @@ msgstr ""
msgid "SAML SSO for %{group_name}"
msgstr ""
msgid "SAML Single Sign On"
msgstr ""
msgid "SAML Single Sign On Settings"
msgstr ""
msgid "SAML for %{group_name}"
msgstr ""
msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
msgstr ""
msgid "SSH Keys"
msgstr ""
......@@ -12466,6 +12490,9 @@ msgstr ""
msgid "Your name"
msgstr ""
msgid "Your new SCIM token"
msgstr ""
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
msgstr ""
......@@ -13336,6 +13363,9 @@ msgid_plural "replies"
msgstr[0] ""
msgstr[1] ""
msgid "reset it."
msgstr ""
msgid "score"
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