Commit 6bd9147f authored by Drew Blessing's avatar Drew Blessing Committed by Drew Blessing

Allow groups to configure SAML group links

Controller and associated components to allow listing, adding
and removing Group SAML group links.
parent 675750b9
......@@ -43,12 +43,6 @@
}
}
.ldap-group-links {
.form-actions {
margin-bottom: $gl-padding;
}
}
.save-group-loader {
margin-top: $gl-padding-50;
margin-bottom: $gl-padding-50;
......
---
name: saml_group_links
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45080
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267020
type: development
group: group::access
default_enabled: false
# frozen_string_literal: true
module Groups
class SamlGroupLinksController < Groups::ApplicationController
before_action :require_saml_group_links_enabled
before_action :authorize_admin_saml_group_links!
layout 'group_settings'
feature_category :authentication_and_authorization
def create
group_link = group.saml_group_links.build(saml_group_link_params)
if group_link.save
flash[:notice] = s_('GroupSAML|New SAML group link saved.')
else
flash[:alert] = alert(group_link.errors.full_messages.join(', '))
end
redirect_to group_saml_group_links_path(@group)
end
def destroy
group.saml_group_links.find(params[:id]).destroy
redirect_to group_saml_group_links_path(@group), status: :found, notice: s_('GroupSAML|SAML group link was successfully removed.')
end
private
def require_saml_group_links_enabled
render_404 unless ::Feature.enabled?(:saml_group_links, group)
end
def authorize_admin_saml_group_links!
access_denied! unless can?(current_user, :admin_saml_group_links, group)
end
def saml_group_link_params
params.require(:saml_group_link).permit(:saml_group_name, :access_level)
end
def alert(error_message)
s_('GroupSAML|Could not create SAML group link: %{errors}.') % { errors: error_message }
end
end
end
......@@ -7,7 +7,7 @@ module EE
end
def show_saml_group_links_in_sidebar?(group)
can?(current_user, :admin_saml_group_links, group)
::Feature.enabled?(:saml_group_links, group) && can?(current_user, :admin_saml_group_links, group)
end
def saml_link_for_provider(text, provider, **args)
......
......@@ -12,6 +12,12 @@
%span
= _('SAML SSO')
- if show_saml_group_links_in_sidebar?(@group)
= nav_link(path: 'saml_group_links#index') do
= link_to group_saml_group_links_path(@group), title: s_('GroupSAML|SAML Group Links') do
%span
= s_('GroupSAML|SAML Group Links')
- if @group.feature_available?(:group_webhooks) || show_promotions?
= nav_link(path: 'hooks#index') do
= link_to group_hooks_path(@group), title: 'Webhooks' do
......
%section.saml-group-links
= form_for [group, SamlGroupLink.new] do |f|
.form-holder
.form-group.row
.col-sm-2.col-form-label
= f.label :saml_group_name, s_('GroupSAML|SAML Group Name')
.col-sm-10
= f.text_field :saml_group_name, class: 'form-control xxlarge input-mn-300'
.form-text.text-muted
= s_('GroupSAML|The case-sensitive group name that will be sent by the SAML identity provider.')
.form-group.row
.col-sm-2.col-form-label
= f.label :access_level, "Access Level"
.col-sm-10
= f.select :access_level, options_for_select(SamlGroupLink.access_levels.keys), {}, class: 'form-control'
.form-text.text-muted
= s_('GroupSAML|Role to assign members of this SAML group.')
.form-actions.gl-mb-5
= f.submit _('Save'), class: 'btn gl-button btn-success'
%li
.float-right
= link_to group_saml_group_link_path(group, saml_group_link), method: :delete, class: 'btn gl-button btn-danger btn-sm', data: { confirm: s_('GroupSAML|Are you sure you want to remove the SAML group link?') } do
= sprite_icon('unlink', size: 12, css_class: 'gl-m-0!')
%span= _('Remove')
%strong= s_('GroupSAML|SAML Group Name: %{saml_group_name}') % { saml_group_name: saml_group_link.saml_group_name }
.light
= s_('GroupSAML|as %{access_level}') % { access_level: saml_group_link.access_level }
- group_links = group.saml_group_links.load
.card
.card-header= s_('GroupSAML|Active SAML Group Links (%{count})') % { count: group_links.size }
- if group_links.any?
%ul.content-list
= render collection: group_links, partial: 'saml_group_link', locals: { group: group }
- else
.card-body
= s_('GroupSAML|No active SAML group links')
- page_title s_('GroupSAML|SAML Group Links')
%h3.page-title= s_('GroupSAML|SAML Group Links')
= render 'form', group: @group
= render 'saml_group_links', group: @group
......@@ -53,5 +53,5 @@
%br
You can manage permission levels for individual group members in the Members tab.
.form-actions
.form-actions.gl-mb-5
= f.submit 'Add synchronization', class: 'btn btn-success qa-add-sync-button'
......@@ -69,6 +69,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
resource :notification_setting, only: [:update]
resources :ldap_group_links, only: [:index, :create, :destroy]
resources :saml_group_links, only: [:index, :create, :destroy]
resources :audit_events, only: [:index]
resources :usage_quotas, only: [:index]
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Groups::SamlGroupLinksController do
let_it_be(:group) { create(:group) }
let_it_be(:user) { create(:user) }
before_all do
group.add_owner(user)
end
before do
stub_licensed_features(group_saml: true)
stub_feature_flags(saml_group_links: true)
sign_in(user)
end
shared_examples 'checks authorization' do
let_it_be(:saml_provider) { create(:saml_provider, group: group, enabled: true) }
let_it_be(:params) { route_params }
it 'renders 404 when the feature is disabled' do
stub_feature_flags(saml_group_links: false)
call_action
expect(response).to have_gitlab_http_status(:not_found)
end
it 'renders 404 when the user is not authorized' do
allow(controller).to receive(:can?).and_call_original
allow(controller).to receive(:can?).with(user, :admin_saml_group_links, group).and_return(false)
call_action
expect(response).to have_gitlab_http_status(:not_found)
end
end
describe '#index' do
let_it_be(:route_params) { { group_id: group } }
subject(:call_action) { get :index, params: params }
it_behaves_like 'checks authorization'
context 'when the SAML provider is enabled' do
let_it_be(:saml_provider) { create(:saml_provider, group: group, enabled: true) }
let_it_be(:params) { route_params }
it 'responds with 200' do
call_action
expect(response).to have_gitlab_http_status(:ok)
end
end
end
describe '#create' do
let_it_be(:route_params) { { group_id: group } }
subject(:call_action) { post :create, params: params }
it_behaves_like 'checks authorization'
context 'when the SAML provider is enabled' do
let_it_be(:saml_provider) { create(:saml_provider, group: group, enabled: true) }
context 'with valid parameters' do
let_it_be(:params) { route_params.merge(saml_group_link: { access_level: 'Reporter', saml_group_name: generate(:saml_group_name) }) }
it 'responds with success' do
call_action
expect(response).to have_gitlab_http_status(:found)
expect(flash[:notice]).to include('New SAML group link saved.')
end
it 'creates the group link' do
expect { call_action }.to change { group.saml_group_links.count }.by(1)
end
end
context 'with missing parameters' do
let_it_be(:params) { route_params.merge(saml_group_link: { access_level: 'Maintainer' }) }
it 'displays an error' do
call_action
expect(response).to have_gitlab_http_status(:found)
expect(flash[:alert]).to include("Could not create SAML group link: Saml group name can't be blank.")
end
end
end
end
describe '#destroy' do
let_it_be(:group_link) { create(:saml_group_link, group: group) }
let_it_be(:route_params) { { group_id: group, id: group_link } }
subject(:call_action) { delete :destroy, params: params }
it_behaves_like 'checks authorization'
context 'when the SAML provider is enabled' do
let_it_be(:saml_provider) { create(:saml_provider, group: group, enabled: true) }
context 'with an existent group link' do
let_it_be(:params) { route_params }
it 'responds with success' do
call_action
expect(response).to have_gitlab_http_status(:found)
expect(flash[:notice]).to include('SAML group link was successfully removed.')
end
it 'removes the group link' do
expect { call_action }.to change { group.saml_group_links.count }.by(-1)
end
end
context 'with a non-existent group link' do
let_it_be(:params) { { group_id: group, id: non_existing_record_id } }
it 'renders 404' do
call_action
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
end
# frozen_string_literal: true
FactoryBot.define do
sequence(:saml_group_name) { |n| "saml-group#{n}" }
factory :saml_group_link do
sequence(:saml_group_name) { |n| "saml-group#{n}" }
saml_group_name { generate(:saml_group_name) }
access_level { Gitlab::Access::GUEST }
group
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'SAML group links' do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
before do
group.add_owner(user)
sign_in(user)
end
context 'when SAML group links is available' do
before do
stub_licensed_features(group_saml: true)
stub_feature_flags(saml_group_links: true)
create(:saml_provider, group: group, enabled: true)
visit group_saml_group_links_path(group)
end
context 'with existing records' do
let_it_be(:group_link1) { create(:saml_group_link, group: group, saml_group_name: 'Web Developers') }
let_it_be(:group_link2) { create(:saml_group_link, group: group, saml_group_name: 'Web Managers') }
let_it_be(:other_group_link) { create(:saml_group_link, group: create(:group), saml_group_name: 'Other Group') }
it 'lists active links' do
expect(page).to have_content('SAML Group Name: Web Developers')
expect(page).to have_content('SAML Group Name: Web Managers')
end
it 'does not list links for other groups' do
expect(page).not_to have_content('SAML Group Name: Other Group')
end
end
it 'adds new SAML group link' do
page.within('form#new_saml_group_link') do
fill_in 'SAML Group Name', with: 'Acme SAML Group'
select 'Developer', from: 'saml_group_link_access_level'
click_button 'Save'
end
expect(page).not_to have_content('No active SAML group links')
expect(page).to have_content('SAML Group Name: Acme SAML Group')
expect(page).to have_content('as Developer')
end
end
end
......@@ -37,20 +37,35 @@ RSpec.describe EE::SamlProvidersHelper do
describe '#show_saml_group_links_in_sidebar?' do
subject { helper.show_saml_group_links_in_sidebar?(group) }
context 'when the user can admin saml group links' do
context 'when the feature is disabled' do
before do
stub_feature_flags(saml_group_links: false)
stub_can(:admin_saml_group_links, true)
end
it { is_expected.to eq(true) }
it { is_expected.to eq(false) }
end
context 'when the user cannot admin saml group links' do
context 'when the feature is enabled' do
before do
stub_can(:admin_saml_group_links, false)
stub_feature_flags(saml_group_links: true)
end
it { is_expected.to eq(false) }
context 'when the user can admin saml group links' do
before do
stub_can(:admin_saml_group_links, true)
end
it { is_expected.to eq(true) }
end
context 'when the user cannot admin saml group links' do
before do
stub_can(:admin_saml_group_links, false)
end
it { is_expected.to eq(false) }
end
end
end
end
......@@ -12909,6 +12909,12 @@ msgstr ""
msgid "GroupRoadmap|To widen your search, change or remove filters; from %{startDate} to %{endDate}."
msgstr ""
msgid "GroupSAML|Active SAML Group Links (%{count})"
msgstr ""
msgid "GroupSAML|Are you sure you want to remove the SAML group link?"
msgstr ""
msgid "GroupSAML|Certificate fingerprint"
msgstr ""
......@@ -12918,6 +12924,9 @@ msgstr ""
msgid "GroupSAML|Copy SAML Response XML"
msgstr ""
msgid "GroupSAML|Could not create SAML group link: %{errors}."
msgstr ""
msgid "GroupSAML|Default membership role"
msgstr ""
......@@ -12963,12 +12972,30 @@ msgstr ""
msgid "GroupSAML|NameID Format"
msgstr ""
msgid "GroupSAML|New SAML group link saved."
msgstr ""
msgid "GroupSAML|No active SAML group links"
msgstr ""
msgid "GroupSAML|Prohibit outer forks"
msgstr ""
msgid "GroupSAML|Prohibit outer forks for this group."
msgstr ""
msgid "GroupSAML|Role to assign members of this SAML group."
msgstr ""
msgid "GroupSAML|SAML Group Links"
msgstr ""
msgid "GroupSAML|SAML Group Name"
msgstr ""
msgid "GroupSAML|SAML Group Name: %{saml_group_name}"
msgstr ""
msgid "GroupSAML|SAML Response Output"
msgstr ""
......@@ -12981,6 +13008,9 @@ msgstr ""
msgid "GroupSAML|SAML Single Sign On Settings"
msgstr ""
msgid "GroupSAML|SAML group link was successfully removed."
msgstr ""
msgid "GroupSAML|SCIM API endpoint URL"
msgstr ""
......@@ -12993,6 +13023,9 @@ msgstr ""
msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
msgstr ""
msgid "GroupSAML|The case-sensitive group name that will be sent by the SAML identity provider."
msgstr ""
msgid "GroupSAML|This will be set as the access level of users added to the group."
msgstr ""
......@@ -13017,6 +13050,9 @@ msgstr ""
msgid "GroupSAML|Your SCIM token"
msgstr ""
msgid "GroupSAML|as %{access_level}"
msgstr ""
msgid "GroupSAML|must match stored NameID of \"%{extern_uid}\" as we use this to identify users. If the NameID changes users will be unable to sign in."
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