Commit da4917c9 authored by Dan Davison's avatar Dan Davison

Merge branch 'qa-shl-fix-group-saml-sso-split-files-and-use-service' into 'master'

Simplify Group SAML SSO specs and run SAML IDP as a service at runtime

Closes #55242

See merge request gitlab-org/gitlab!24172
parents 56a03c42 744071c5
......@@ -16,6 +16,8 @@ module QA
module Flow
autoload :Login, 'qa/flow/login'
autoload :Project, 'qa/flow/project'
autoload :Saml, 'qa/flow/saml'
autoload :User, 'qa/flow/user'
end
##
......@@ -431,6 +433,7 @@ module QA
autoload :NodeJs, 'qa/service/docker_run/node_js'
autoload :GitlabRunner, 'qa/service/docker_run/gitlab_runner'
autoload :MailHog, 'qa/service/docker_run/mail_hog'
autoload :SamlIdp, 'qa/service/docker_run/saml_idp'
end
end
......
......@@ -30,40 +30,40 @@ module QA
fill_element :certificate_fingerprint_field, fingerprint
end
def enforce_sso
Support::Retrier.retry_until(sleep_interval: 1.0, raise_on_failure: true) do
click_element :enforced_sso_toggle_button unless find_element(:enforced_sso_toggle_button)[:class].include?('is-checked')
find_element(:enforced_sso_toggle_button)[:class].include?('is-checked')
end
def has_enforced_sso_button?
has_element?(:enforced_sso_toggle_button, wait: 1.0)
end
def has_enforce_sso_button?
has_element?(:enforced_sso_toggle_button, wait: 1.0)
def enforce_sso_enabled?
has_enforced_sso_button? && find_element(:enforced_sso_toggle_button)[:class].include?('is-checked')
end
def enforce_sso
click_element :enforced_sso_toggle_button unless enforce_sso_enabled?
Support::Waiter.wait_until(raise_on_failure: true) { enforce_sso_enabled? }
end
def disable_enforce_sso
Support::Retrier.retry_until(sleep_interval: 1.0, raise_on_failure: true) do
click_element :enforced_sso_toggle_button if find_element(:enforced_sso_toggle_button)[:class].include?('is-checked')
!find_element(:enforced_sso_toggle_button)[:class].include?('is-checked')
end
def disable_enforced_sso
click_element :enforced_sso_toggle_button if enforce_sso_enabled?
Support::Waiter.wait_until(raise_on_failure: true) { !enforce_sso_enabled? }
end
def has_group_managed_accounts_button?
has_element?(:group_managed_accounts_toggle_button, wait: 1.0)
end
def group_managed_accounts_enabled?
enforce_sso_enabled? && has_group_managed_accounts_button? && find_element(:group_managed_accounts_toggle_button)[:class].include?('is-checked')
end
def enable_group_managed_accounts
Support::Retrier.retry_until(sleep_interval: 1.0, raise_on_failure: true) do
click_element :group_managed_accounts_toggle_button unless find_element(:group_managed_accounts_toggle_button)[:class].include?('is-checked')
find_element(:group_managed_accounts_toggle_button)[:class].include?('is-checked')
end
click_element :group_managed_accounts_toggle_button unless group_managed_accounts_enabled?
Support::Waiter.wait_until { group_managed_accounts_enabled? }
end
def disable_group_managed_accounts
Support::Retrier.retry_until(sleep_interval: 1.0, raise_on_failure: true) do
click_element :group_managed_accounts_toggle_button if find_element(:group_managed_accounts_toggle_button)[:class].include?('is-checked')
!find_element(:group_managed_accounts_toggle_button)[:class].include?('is-checked')
end
click_element :group_managed_accounts_toggle_button if group_managed_accounts_enabled?
Support::Waiter.wait_until { !group_managed_accounts_enabled? }
end
def click_save_changes
......
# frozen_string_literal: true
module QA
module Flow
module Saml
module_function
def page
Capybara.current_session
end
def logout_from_idp(saml_idp_service)
Runtime::Logger.debug("Logging out of IDP by visiting \"#{saml_idp_service.idp_sign_out_url}\"")
Support::Waiter.wait_until(sleep_interval: 1, reload_page: page) do
page.visit saml_idp_service.idp_sign_out_url
page.has_content?("You have been logged out.")
end
end
def enable_saml_sso(group, saml_idp_service)
page.visit Runtime::Scenario.gitlab_address
Page::Main::Login.perform(&:sign_in_using_credentials) unless Page::Main::Menu.perform(&:signed_in?)
visit_saml_sso_settings(group)
Support::Retrier.retry_on_exception do
EE::Page::Group::Settings::SamlSSO.perform do |saml_sso|
saml_sso.set_id_provider_sso_url(saml_idp_service.idp_sso_url)
saml_sso.set_cert_fingerprint(saml_idp_service.idp_certificate_fingerprint)
saml_sso.click_save_changes
saml_sso.user_login_url_link_text
end
end
end
def visit_saml_sso_settings(group, direct: false)
if direct
page.visit "#{group.web_url}/-/saml"
else
group.visit!
Page::Group::Menu.perform(&:go_to_saml_sso_group_settings)
end
# The toggle buttons take a moment to switch to the correct status.
# I am not sure of a better, less complex way to wait for them to reflect their actual status.
sleep 2
end
def run_saml_idp_service(group_name)
service = Service::DockerRun::SamlIdp.new(Runtime::Scenario.gitlab_address, group_name).tap do |runner|
runner.pull
runner.register!
end
service
end
def remove_saml_idp_service(saml_idp_service)
saml_idp_service.remove!
end
def login_to_idp_if_required(username, password)
Vendor::SAMLIdp::Page::Login.perform { |login_page| login_page.login_if_required(username, password) }
end
end
end
end
# frozen_string_literal: true
module QA
module Flow
module User
module_function
def page
Capybara.current_session
end
def confirm_user(username)
Flow::Login.while_signed_in_as_admin do
Page::Main::Menu.perform(&:go_to_admin_area)
Page::Admin::Menu.perform(&:go_to_users_overview)
Page::Admin::Overview::Users::Index.perform do |index|
index.search_user(username)
index.click_user(username)
end
Page::Admin::Overview::Users::Show.perform(&:confirm_user)
end
end
end
end
end
......@@ -11,6 +11,10 @@ module QA
post Runtime::API::Request.new(api_client, api_members_path).url, { user_id: user.id, access_level: access_level }
end
def remove_member(user)
delete Runtime::API::Request.new(api_client, "#{api_members_path}/#{user.id}").url
end
def list_members
JSON.parse(get(Runtime::API::Request.new(api_client, api_members_path).url).body)
end
......
......@@ -63,6 +63,10 @@ module QA
'/groups'
end
def api_delete_path
"/groups/#{id}"
end
def api_post_body
{
path: path,
......
......@@ -38,6 +38,8 @@ module QA
end
raise SetFeatureError, "#{key} was not enabled!" unless is_enabled
QA::Runtime::Logger.info("Successfully enabled and verified feature flag: #{key}")
end
end
......
# frozen_string_literal: true
module QA
module Service
module DockerRun
class SamlIdp < Base
def initialize(gitlab_host, group)
@image = 'jamedjo/test-saml-idp'
@name = 'saml-idp-server'
@gitlab_host = gitlab_host
@group = group
super()
end
def idp_base_url
"https://#{host_name}:8443/simplesaml"
end
def idp_sso_url
"#{idp_base_url}/saml2/idp/SSOService.php"
end
def idp_sign_out_url
"#{idp_base_url}/module.php/core/authenticate.php?as=example-userpass&logout"
end
def idp_signed_out_url
"#{idp_base_url}/logout.php"
end
def idp_metadata_url
"#{idp_base_url}/saml2/idp/metadata.php"
end
def idp_issuer
idp_metadata_url
end
def idp_certificate_fingerprint
QA::Runtime::Env.simple_saml_fingerprint || '119b9e027959cdb7c662cfd075d9e2ef384e445f'
end
def host_name
return 'localhost' unless QA::Runtime::Env.running_in_ci?
super
end
def register!
command = <<~CMD.tr("\n", ' ')
docker run -d --rm
--network #{network}
--hostname #{host_name}
--name #{@name}
--env SIMPLESAMLPHP_SP_ENTITY_ID=#{@gitlab_host}/groups/#{@group}
--env SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE=#{@gitlab_host}/groups/#{@group}/-/saml/callback
--publish 8080:8080
--publish 8443:8443
#{@image}
CMD
command.gsub!("--network #{network} ", '') unless QA::Runtime::Env.running_in_ci?
shell command
end
end
end
end
end
# frozen_string_literal: true
module QA
context 'Manage', :group_saml, :orchestrated, :requires_admin do
describe 'Group SAML SSO - Enforced SSO' do
include Support::Api
before(:all) do
@group = Resource::Sandbox.fabricate_via_api! do |sandbox_group|
sandbox_group.path = "saml_sso_group_#{SecureRandom.hex(8)}"
end
@developer_user = Resource::User.fabricate_via_api!
@group.add_member(@developer_user)
@saml_idp_service = Flow::Saml.run_saml_idp_service(@group.path)
@managed_group_url = setup_and_enable_enforce_sso
end
before do
Flow::Saml.logout_from_idp(@saml_idp_service)
page.visit Runtime::Scenario.gitlab_address
Page::Main::Menu.perform(&:sign_out_if_signed_in)
end
context 'Access' do
let(:project) do
Resource::Project.fabricate! do |project|
project.name = 'project-in-saml-enforced-group-for-access-test'
project.description = 'project in SAML enforced group for access test'
project.group = @group
project.initialize_with_readme = true
project.visibility = 'private'
end
end
let(:sub_group) do
Resource::Group.fabricate_via_api! do |group|
group.sandbox = @group
group.path = "saml-sub-group"
end
end
let(:sub_group_project) do
Resource::Project.fabricate! do |project|
project.name = 'sub-group-project-in-saml-enforced-group-for-access-test'
project.description = 'Sub Group project in SAML enforced group for access test'
project.group = sub_group
project.initialize_with_readme = true
project.visibility = 'private'
end
end
shared_examples 'user access' do
it 'is not allowed without SSO' do
Page::Main::Login.perform do |login|
login.sign_in_using_credentials(user: user)
end
expected_single_signon_text = 'group allows you to sign in with your Single Sign-On Account'
@group.visit!
expect(page).to have_content(expected_single_signon_text)
sub_group.visit!
expect(page).to have_content(expected_single_signon_text)
project.visit!
expect(page).to have_content(expected_single_signon_text)
sub_group_project.visit!
expect(page).to have_content(expected_single_signon_text)
end
end
before(:all) do
@owner_user = Resource::User.fabricate_via_api!
@group.add_member(@owner_user, Resource::Members::AccessLevel::OWNER)
end
after(:all) do
@group.remove_member(@owner_user)
end
it_behaves_like 'user access' do
let(:user) { @developer_user }
end
it_behaves_like 'user access' do
let(:user) { @owner_user }
end
end
it 'user clones and pushes to project within a group using Git HTTP' do
Flow::Login.sign_in
@project = Resource::Project.fabricate! do |project|
project.name = 'project-in-saml-enforced-group'
project.description = 'project in SAML enforced group for git clone test'
project.group = @group
project.initialize_with_readme = true
end
@project.visit!
expect do
Resource::Repository::ProjectPush.fabricate! do |project_push|
project_push.project = @project
project_push.branch_name = "new_branch"
project_push.user = @developer_user
end
end.not_to raise_error
end
after(:all) do
page.visit Runtime::Scenario.gitlab_address
%w[enforced_sso enforced_sso_requires_session].each do |flag|
Runtime::Feature.remove(flag)
end
@group.remove_via_api!
Page::Main::Menu.perform(&:sign_out_if_signed_in)
Flow::Saml.remove_saml_idp_service(@saml_idp_service)
end
end
def setup_and_enable_enforce_sso
%w[enforced_sso enforced_sso_requires_session].each do |flag|
Runtime::Feature.enable_and_verify(flag)
end
page.visit Runtime::Scenario.gitlab_address
Page::Main::Login.perform(&:sign_in_using_credentials) unless Page::Main::Menu.perform(&:signed_in?)
Support::Retrier.retry_on_exception do
Flow::Saml.visit_saml_sso_settings(@group)
managed_group_url = EE::Page::Group::Settings::SamlSSO.perform do |saml_sso|
# Once the feature flags are enabled, it takes some time for the toggle buttons to show on the UI.
# This issue does not happen manually. Only happens with the test as they are too fast.
Support::Retrier.retry_until(sleep_interval: 1, raise_on_failure: true) do
condition_met = saml_sso.has_enforced_sso_button?
page.refresh unless condition_met
condition_met
end
saml_sso.enforce_sso
saml_sso.set_id_provider_sso_url(@saml_idp_service.idp_sso_url)
saml_sso.set_cert_fingerprint(@saml_idp_service.idp_certificate_fingerprint)
saml_sso.click_save_changes
saml_sso.user_login_url_link_text
end
Flow::Saml.visit_saml_sso_settings(@group, direct: true)
raise "Enforced SSO not setup correctly" unless EE::Page::Group::Settings::SamlSSO.perform(&:enforce_sso_enabled?)
managed_group_url
end
end
end
end
# frozen_string_literal: true
module QA
context 'Manage', :group_saml, :orchestrated, :requires_admin, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/issues/202260', type: :bug } do
describe 'Group SAML SSO - Group managed accounts' do
include Support::Api
before(:all) do
# Create a new user (with no existing SAML identities) who will be added as owner to the SAML group.
@owner_user = Resource::User.fabricate_via_api!
Flow::Login.sign_in(as: @owner_user)
@group = Resource::Sandbox.fabricate_via_api! do |sandbox_group|
sandbox_group.path = "saml_sso_group_#{SecureRandom.hex(8)}"
end
@saml_idp_service = Flow::Saml.run_saml_idp_service(@group.path)
@api_client = Runtime::API::Client.new(:gitlab, personal_access_token: Runtime::Env.admin_personal_access_token)
@developer_user = Resource::User.fabricate_via_api!
@group.add_member(@owner_user, QA::Resource::Members::AccessLevel::OWNER)
@group.add_member(@developer_user)
@managed_group_url = Flow::Saml.enable_saml_sso(@group, @saml_idp_service)
@saml_linked_for_admin = false
setup_and_enable_group_managed_accounts
Page::Main::Menu.perform(&:sign_out_if_signed_in)
Flow::Saml.logout_from_idp(@saml_idp_service)
end
it 'removes existing users from the group, forces existing users to create a new account and allows to leave group' do
expect(@group.list_members.map { |item| item["username"] }).not_to include(@developer_user.username)
visit_managed_group_url
EE::Page::Group::SamlSSOSignIn.perform(&:click_sign_in)
Flow::Saml.login_to_idp_if_required('user3', 'user3pass')
expect(page).to have_text("uses group managed accounts. You need to create a new GitLab account which will be managed by")
Support::Retrier.retry_until(raise_on_failure: true) do
@idp_user_email = EE::Page::Group::SamlSSOSignUp.perform(&:current_email)
remove_user_if_exists(@idp_user_email)
@new_username = EE::Page::Group::SamlSSOSignUp.perform(&:current_username)
EE::Page::Group::SamlSSOSignUp.perform(&:click_register_button)
page.has_no_content?("Email has already been taken")
end
expect(page).to have_text("Sign up was successful! Please confirm your email to sign in.")
QA::Flow::User.confirm_user(@new_username)
visit_managed_group_url
EE::Page::Group::SamlSSOSignIn.perform(&:click_sign_in)
expect(page).to have_text("Signed in with SAML")
Page::Group::Show.perform(&:leave_group)
expect(page).to have_text("You left")
Page::Main::Menu.perform(&:sign_out)
visit_managed_group_url
EE::Page::Group::SamlSSOSignIn.perform(&:click_sign_in)
expect(page).to have_text("uses group managed accounts. You need to create a new GitLab account which will be managed by")
end
after(:all) do
page.visit Runtime::Scenario.gitlab_address
%w[enforced_sso enforced_sso_requires_session group_managed_accounts sign_up_on_sso group_scim].each do |flag|
Runtime::Feature.remove(flag)
end
remove_user_if_exists(@idp_user_email)
@group.remove_via_api!
Flow::Saml.remove_saml_idp_service(@saml_idp_service)
page.visit Runtime::Scenario.gitlab_address
Page::Main::Menu.perform(&:sign_out_if_signed_in)
end
end
def remove_user_if_exists(username_or_email)
QA::Runtime::Logger.debug("Attempting to remove user \"#{username_or_email}\" via API")
return if username_or_email.nil?
response = parse_body(get Runtime::API::Request.new(@api_client, "/users?search=#{username_or_email}").url)
if response.any?
raise "GET /users?search=#{username_or_email} returned multiple results. response: #{response}" if response.size > 1
delete_response = delete Runtime::API::Request.new(@api_client, "/users/#{response.first[:id]}").url
QA::Runtime::Logger.debug("DELETE \"#{username_or_email}\" response code: #{delete_response.code} message: #{delete_response.body}")
else
QA::Runtime::Logger.debug("GET /users?search=#{username_or_email} returned empty response: #{response}")
end
end
def setup_and_enable_group_managed_accounts
%w[enforced_sso enforced_sso_requires_session group_managed_accounts sign_up_on_sso group_scim].each do |flag|
Runtime::Feature.enable_and_verify(flag)
end
Support::Retrier.retry_on_exception do
# We need to logout from IDP. This is required if this is a retry.
Flow::Saml.logout_from_idp(@saml_idp_service)
page.visit Runtime::Scenario.gitlab_address
Page::Main::Menu.perform(&:sign_out_if_signed_in)
# The first time you have to be signed in as admin
unless @saml_linked_for_admin
Flow::Login.sign_in(as: @owner_user)
@saml_linked_for_admin = true
end
# We must sign in with SAML before enabling Group Managed Accounts
visit_managed_group_url
EE::Page::Group::SamlSSOSignIn.perform(&:click_sign_in)
Flow::Saml.login_to_idp_if_required('user1', 'user1pass')
Flow::Saml.visit_saml_sso_settings(@group)
EE::Page::Group::Settings::SamlSSO.perform do |saml_sso|
# Once the feature flags are enabled, it takes some time for the toggle buttons to show on the UI.
# This issue does not happen manually. Only happens with the test as they are too fast.
Support::Retrier.retry_until(sleep_interval: 1, raise_on_failure: true) do
condition_met = saml_sso.has_enforced_sso_button? && saml_sso.has_group_managed_accounts_button?
page.refresh unless condition_met
condition_met
end
saml_sso.enforce_sso
saml_sso.enable_group_managed_accounts
saml_sso.click_save_changes
saml_sso.user_login_url_link_text
end
Flow::Saml.visit_saml_sso_settings(@group, direct: true)
raise "Group managed accounts not setup correctly" unless EE::Page::Group::Settings::SamlSSO.perform(&:group_managed_accounts_enabled?)
end
end
def visit_managed_group_url
Runtime::Logger.debug(%Q[Visiting managed_group_url at "#{@managed_group_url}"])
page.visit @managed_group_url
Support::Waiter.wait_until { current_url == @managed_group_url }
end
end
end
# frozen_string_literal: true
module QA
context 'Manage', :group_saml, :orchestrated, :requires_admin do
describe 'Group SAML SSO - Non enforced SSO' do
include Support::Api
before(:all) do
@group = Resource::Sandbox.fabricate_via_api! do |sandbox_group|
sandbox_group.path = "saml_sso_group_#{SecureRandom.hex(8)}"
end
@saml_idp_service = Flow::Saml.run_saml_idp_service(@group.path)
end
before do
Flow::Saml.logout_from_idp(@saml_idp_service)
page.visit Runtime::Scenario.gitlab_address
Page::Main::Menu.perform(&:sign_out_if_signed_in)
Flow::Login.sign_in
end
it 'User logs in to group with SAML SSO' do
managed_group_url = Flow::Saml.enable_saml_sso(@group, @saml_idp_service)
page.visit managed_group_url
EE::Page::Group::SamlSSOSignIn.perform(&:click_sign_in)
Flow::Saml.login_to_idp_if_required('user1', 'user1pass')
expect(page).to have_content("SAML for #{@group.path} was added to your connected accounts")
page.visit managed_group_url
EE::Page::Group::SamlSSOSignIn.perform(&:click_sign_in)
expect(page).to have_content("Already signed in with SAML for #{@group.path}")
end
it 'Lets group admin test settings' do
incorrect_fingerprint = Digest::SHA1.hexdigest(rand.to_s)
Flow::Saml.visit_saml_sso_settings(@group)
EE::Page::Group::Settings::SamlSSO.perform do |saml_sso|
saml_sso.set_id_provider_sso_url(@saml_idp_service.idp_sso_url)
saml_sso.set_cert_fingerprint(incorrect_fingerprint)
saml_sso.click_save_changes
saml_sso.click_test_button
end
Flow::Saml.login_to_idp_if_required('user2', 'user2pass')
expect(page).to have_content("Verify SAML Configuration")
expect(page).to have_content("Fingerprint mismatch")
expect(page).to have_content("<saml:Issuer>#{@saml_idp_service.idp_issuer}</saml:Issuer>")
EE::Page::Group::Settings::SamlSSO.perform do |saml_sso|
saml_sso.set_cert_fingerprint(@saml_idp_service.idp_certificate_fingerprint)
saml_sso.click_save_changes
saml_sso.click_test_button
end
expect(page).to have_content("Verify SAML Configuration")
expect(page).not_to have_content("Fingerprint mismatch")
end
after(:all) do
@group.remove_via_api!
page.visit Runtime::Scenario.gitlab_address
Page::Main::Menu.perform(&:sign_out_if_signed_in)
Flow::Saml.remove_saml_idp_service(@saml_idp_service)
end
end
end
end
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