Browse Source

Maintenance: Extend desktop view login tests.

(cherry picked from commit 106f9d9dbcd212d832f83e76bbc555bef09e2b30)

2e0cb018 Maintenance: Extend desktop view login tests.
23845527 Drop tests that are not specified in the scenario.
8d8ea2b3 Added stub for omniauth test.
a3edc91f Add first version of SAML test, pending refactoring.
ee7aa834 Added SAML to login test, refactored.
7c2a6215 Silence gitleaks.
ba5cec7f Also test for logout.
6850d48b Verify SAML login and logout.
f4d6c25c Verify SAML logout, wip
c04c8a6a Improved waiting, wip
0ab1e656 Apply workarounds for broken SAML redirects in CI.
fa4ab636 Refactor logout handling.
1ea7b307 Improve check for mobile SAML spec
922ba3a4 Revert "Improve check for mobile SAML spec"
4951543c Use :stable tag for zammad-auth docker image as for all others.
ae641213 Switch saml_spec.rb to be compatible to keycloak 24.
53231e18 Revert unneeded changes to common_actions.rb.
727e4d89 Improve checking for logged-in state.
Martin Gruner 11 months ago
parent
commit
89d64b2ef4

+ 1 - 1
.gitlab/ci/__includes__/services.yml

@@ -101,7 +101,7 @@
     alias: ci-service-proxy
 
   auth:
-    name: $CI_REGISTRY/docker/zammad-auth:develop
+    name: $CI_REGISTRY/docker/zammad-auth:stable
     alias: ci-service-auth
 
   mattermost:

+ 2 - 2
.rubocop/default.yml

@@ -373,7 +373,7 @@ RSpec/DescribeMethod:
 RSpec/NestedGroups:
   Max: 6
 
-RSpec/Rails/AvoidSetupHook:
+RSpecRails/AvoidSetupHook:
   Exclude:
     - "test/**/*"
 
@@ -388,7 +388,7 @@ RSpec/AnyInstance:
 RSpec/SubjectStub:
   Enabled: false
 
-RSpec/Rails/InferredSpecType:
+RSpecRails/InferredSpecType:
   Description: 'Identifies redundant spec type.'
   Enabled: false  # We use types to add DSL to rspec.
 

+ 76 - 0
spec/support/saml.rb

@@ -0,0 +1,76 @@
+# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
+
+require 'keycloak/admin'
+
+module ZammadSpecSupportSAML
+
+  def saml_configure_keycloak(zammad_saml_metadata:, saml_client_json:)
+    # Setup Keycloak SAML authentication.
+    if !Keycloak::Admin.configured?
+      Keycloak::Admin.configure do |config|
+        config.username = ENV['KEYCLOAK_ADMIN_USER']
+        config.password = ENV['KEYCLOAK_ADMIN_PASSWORD']
+        config.realm    = 'zammad'
+        config.base_url = ENV['KEYCLOAK_BASE_URL']
+      end
+    end
+
+    # Force create Zammad client in Keycloak.
+    client = Keycloak::Admin.clients.lookup(clientId: zammad_saml_metadata)
+    if client.count.positive?
+      Keycloak::Admin.clients.delete(client.first['id'])
+    end
+    Keycloak::Admin.clients.create(JSON.parse(saml_client_json))
+  end
+
+  def saml_configure_zammad(saml_base_url:, saml_realm_zammad_descriptor:, name_identifier_format: nil, uid_attribute: nil, idp_slo_service_url: true, security: nil)
+    # Setup Zammad SAML authentication.
+    response = UserAgent.get(saml_realm_zammad_descriptor)
+    raise 'No Zammad realm descriptor found' if !response.success?
+
+    match = response.body.match(%r{<ds:X509Certificate>(?<cert>.+)</ds:X509Certificate>})
+    raise 'No X509Certificate found' if !match[:cert]
+
+    auth_saml_credentials =
+      {
+        display_name:           'SAML',
+        idp_sso_target_url:     "#{saml_base_url}/realms/zammad/protocol/saml",
+        idp_cert:               "-----BEGIN CERTIFICATE-----\n#{match[:cert]}\n-----END CERTIFICATE-----",
+        name_identifier_format: name_identifier_format || 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
+      }
+    auth_saml_credentials[:idp_slo_service_url] = "#{saml_base_url}/realms/zammad/protocol/saml" if idp_slo_service_url
+    auth_saml_credentials[:uid_attribute] = uid_attribute if uid_attribute
+
+    if security.present?
+      auth_saml_credentials[:security] = 'on'
+      auth_saml_credentials[:certificate] = "-----BEGIN CERTIFICATE-----\n#{security[:cert]}\n-----END CERTIFICATE-----"
+      auth_saml_credentials[:private_key] = "-----BEGIN RSA PRIVATE KEY-----\n#{security[:key]}\n-----END RSA PRIVATE KEY-----" # gitleaks:allow
+      auth_saml_credentials[:private_key_secret] = ''
+    end
+
+    # Disable setting validation. We have an explicit test for this.
+    setting = Setting.find_by(name: 'auth_saml_credentials')
+    setting.state_current = { value: auth_saml_credentials }
+    setting.save!(validate: false)
+
+    Setting.set('auth_saml', true)
+  end
+
+  def saml_login_keycloak
+    find_by_id('kc-form')
+    expect(page).to have_current_path(%r{/realms/zammad/protocol/saml\?SAMLRequest=.+})
+    expect(page).to have_css('#kc-form-login')
+
+    within '#kc-form-login' do
+      fill_in 'username', with: 'john.doe'
+      fill_in 'password', with: 'test'
+
+      click_on 'Sign In'
+    end
+  end
+
+end
+
+RSpec.configure do |config|
+  config.include ZammadSpecSupportSAML, integration_standalone: :saml
+end

+ 57 - 24
spec/system/apps/desktop/login_spec.rb

@@ -3,40 +3,73 @@
 require 'rails_helper'
 
 RSpec.describe 'Desktop > Login', app: :desktop_view, authenticated_as: false, type: :system do
-  it 'Login with remember me and logout again' do
-    visit '/login', skip_waiting: true
+  context 'when logging in with two factor auth' do
+    let(:user)                 { User.find_by(login: 'admin@example.com') }
+    let(:code)                 { two_factor_pref.configuration[:code] }
+    let(:recover_code_enabled) { false }
+    let!(:two_factor_pref)     { create(:user_two_factor_preference, :authenticator_app, user:) }
+    let(:token)                { 'token' }
 
-    login(
-      username:    'admin@example.com',
-      password:    'test',
-      remember_me: true,
-    )
+    before do
+      visit '/login'
 
-    expect_current_route '/'
+      login(
+        username:     'admin@example.com',
+        password:     'test',
+        remember_me:  true,
+        skip_waiting: true,
+      )
+    end
 
-    refresh
+    it 'can login with correct code' do
+      expect(page).to have_no_text('Try another method')
 
-    cookie = cookie('^_zammad.+?')
-    expect(cookie[:expires]).to be_truthy
+      find_input('Security Code').type(code)
+      find_button('Sign in').click
 
-    logout
-    expect_current_route 'login'
+      expect(page).to have_text('Logout')
 
-    # Check that cookies has no longer a expire date after logout.
-    cookie = cookie('^_zammad.+?')
-    expect(cookie[:expires]).to be_nil
+      logout
+      expect(page).to have_text('Sign in')
+    end
   end
 
-  it 'Login and redirect to requested url' do
-    visit '/playground', skip_waiting: true
+  context 'when loggin in via external authentication provider', authenticated_as: false, integration: true, integration_standalone: :saml, required_envs: %w[KEYCLOAK_BASE_URL KEYCLOAK_ADMIN_USER KEYCLOAK_ADMIN_PASSWORD] do
+    let(:zammad_base_url)              { "#{Capybara.app_host}:#{Capybara.current_session.server.port}" }
+    let(:zammad_saml_metadata)         { "#{zammad_base_url}/auth/saml/metadata" }
+    let(:saml_base_url)                { ENV['KEYCLOAK_BASE_URL'] }
+    let(:saml_client_json)             { Rails.root.join('test/data/saml/zammad-client.json').read.gsub('#ZAMMAD_BASE_URL', zammad_base_url) }
+    let(:saml_realm_zammad_descriptor) { "#{saml_base_url}/realms/zammad/protocol/saml/descriptor" }
+    let(:saml_realm_zammad_accounts)   { "#{saml_base_url}/realms/zammad/account" }
 
-    expect_current_route '/login?redirect=/playground' # TODO: FIX route to valid desktop view route instead of playground
+    before do
+      saml_configure_keycloak(zammad_saml_metadata:, saml_client_json:)
+      saml_configure_zammad(saml_base_url:, saml_realm_zammad_descriptor:)
+    end
 
-    login(
-      username: 'admin@example.com',
-      password: 'test',
-    )
+    it 'can login via external authentication provider' do
+      visit '/login'
+      expect(page).to have_text('Or sign in using')
+      expect(page).to have_text('SAML')
 
-    expect_current_route '/playground'
+      find_button('SAML').click
+
+      saml_login_keycloak
+
+      # Workaround: SAML redirects in CI don't work because of missing HTTP referrer headers.
+      visit '/'
+      expect(page).to have_text('Logout')
+
+      # Manual logout
+      click_button 'Logout' # rubocop:disable Capybara/ClickLinkOrButtonStyle
+      expect(page).to have_current_path(%r{/login})
+      wait_for_test_flag('applicationLoaded.loaded', skip_clearing: true)
+
+      visit '/'
+      expect_current_route '/login'
+
+      visit saml_realm_zammad_accounts
+      expect(page).to have_text('Sign in')
+    end
   end
 end

+ 37 - 95
spec/system/apps/mobile/login_spec.rb

@@ -3,90 +3,15 @@
 require 'rails_helper'
 
 RSpec.describe 'Mobile > Login', app: :mobile, authenticated_as: false, type: :system do
-  it 'Login with remember me and logout again' do
-    visit '/login', skip_waiting: true
-
-    login(
-      username:    'admin@example.com',
-      password:    'test',
-      remember_me: true,
-    )
-
-    expect_current_route '/'
-
-    refresh
-
-    cookie = cookie('^_zammad.+?')
-    expect(cookie[:expires]).to be_truthy
-
-    logout
-    expect_current_route 'login'
-
-    # Check that cookies has no longer a expire date after logout.
-    cookie = cookie('^_zammad.+?')
-    expect(cookie[:expires]).to be_nil
-  end
-
-  it 'Login and redirect to requested url' do
-    visit '/notifications', skip_waiting: true
-
-    expect_current_route '/login?redirect=/notifications'
-
-    login(
-      username: 'admin@example.com',
-      password: 'test',
-    )
-
-    expect_current_route '/notifications'
-  end
-
-  it 'Shows public links' do
-    link = create(:public_link)
-    visit '/login', skip_waiting: true
-
-    wait_for_gql('shared/entities/public-links/graphql/queries/links.graphql')
-
-    expect(page).to have_link(link.title, href: link.link)
-
-    link.update!(title: 'new link')
-
-    wait_for_gql('shared/entities/public-links/graphql/subscriptions/currentLinks.graphql')
-
-    expect(page).to have_link('new link', href: link.link)
-  end
-
-  context 'when after auth is required' do
-    it 'requires setting up two factor auth' do
-      allow_any_instance_of(Auth::AfterAuth::TwoFactorConfiguration).to receive(:check).and_return(true)
-
-      visit '/login', skip_waiting: true
-
-      login(
-        username:     'admin@example.com',
-        password:     'test',
-        remember_me:  true,
-        skip_waiting: true,
-      )
-
-      expect(page).to have_content('The two-factor authentication is not configured yet')
-      expect_current_route '/login/after-auth'
-    end
-  end
-
   context 'when logging in with two factor auth' do
     let(:user)                 { User.find_by(login: 'admin@example.com') }
     let(:code)                 { two_factor_pref.configuration[:code] }
     let(:recover_code_enabled) { false }
     let!(:two_factor_pref)     { create(:user_two_factor_preference, :authenticator_app, user:) }
     let(:token)                { 'token' }
-    let(:recovery_2fa)         { create(:user_two_factor_preference, :recovery_codes, recovery_code: token, user:) }
 
     before do
-      stub_const('Auth::BRUTE_FORCE_SLEEP', 0)
-      recovery_2fa if recover_code_enabled
-      Setting.set('two_factor_authentication_recovery_codes', recover_code_enabled)
-
-      visit '/login', skip_waiting: true
+      visit '/login'
 
       login(
         username:     'admin@example.com',
@@ -102,33 +27,50 @@ RSpec.describe 'Mobile > Login', app: :mobile, authenticated_as: false, type: :s
       find_input('Security Code').type(code)
       find_button('Sign in').click
 
-      expect_current_route '/'
+      expect(page).to have_text('Home')
+
+      logout
+      expect_current_route '/login'
+    end
+  end
+
+  context 'when loggin in via external authentication provider', authenticated_as: false, integration: true, integration_standalone: :saml, required_envs: %w[KEYCLOAK_BASE_URL KEYCLOAK_ADMIN_USER KEYCLOAK_ADMIN_PASSWORD] do
+    let(:zammad_base_url)              { "#{Capybara.app_host}:#{Capybara.current_session.server.port}" }
+    let(:zammad_saml_metadata)         { "#{zammad_base_url}/auth/saml/metadata" }
+    let(:saml_base_url)                { ENV['KEYCLOAK_BASE_URL'] }
+    let(:saml_client_json)             { Rails.root.join('test/data/saml/zammad-client.json').read.gsub('#ZAMMAD_BASE_URL', zammad_base_url) }
+    let(:saml_realm_zammad_descriptor) { "#{saml_base_url}/realms/zammad/protocol/saml/descriptor" }
+    let(:saml_realm_zammad_accounts)   { "#{saml_base_url}/realms/zammad/account" }
+
+    before do
+      saml_configure_keycloak(zammad_saml_metadata:, saml_client_json:)
+      saml_configure_zammad(saml_base_url:, saml_realm_zammad_descriptor:)
     end
 
-    context 'when logging in with recovery code' do
-      let(:recover_code_enabled) { true }
+    it 'can login via external authentication provider' do
+      visit '/login'
+      expect(page).to have_text('Or sign in using')
+      expect(page).to have_text('SAML')
 
-      before do
-        find_button('Try another method').click
-        find_button('Or use one of your recovery codes.').click
-      end
+      find_button('SAML').click
 
-      it 'can login with correct code' do
-        find_input('Recovery Code').type(token)
-        find_button('Sign in').click
+      saml_login_keycloak
 
-        expect_current_route '/'
-      end
+      # Workaround: SAML redirects in CI don't work because of missing HTTP referrer headers.
+      visit '/'
+      expect(page).to have_text('Home')
 
-      it 'shows an error with incorrect code' do
-        find_input('Recovery Code').type('incorrect')
-        find_button('Sign in').click
+      # Manual logout
+      click_on 'JD' # avatar
+      click_on 'Sign out'
+      expect(page).to have_current_path(%r{/login})
+      wait_for_test_flag('applicationLoaded.loaded', skip_clearing: true)
 
-        expect(page).to have_text('Please double-check your two-factor authentication method')
+      visit '/'
+      expect_current_route '/login'
 
-        expect_current_route '/login'
-      end
+      visit saml_realm_zammad_accounts
+      expect(page).to have_text('Sign in')
     end
   end
-
 end

+ 27 - 94
spec/system/saml_spec.rb

@@ -14,55 +14,7 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
 
   # Only before(:each) can access let() variables.
   before do
-    # Setup Keycloak SAML authentication.
-    if !Keycloak::Admin.configured?
-      Keycloak::Admin.configure do |config|
-        config.username = ENV['KEYCLOAK_ADMIN_USER']
-        config.password = ENV['KEYCLOAK_ADMIN_PASSWORD']
-        config.realm    = 'zammad'
-        config.base_url = ENV['KEYCLOAK_BASE_URL']
-      end
-    end
-
-    # Force create Zammad client in Keycloak.
-    client = Keycloak::Admin.clients.lookup(clientId: zammad_saml_metadata)
-    if client.count.positive?
-      Keycloak::Admin.clients.delete(client.first['id'])
-    end
-    Keycloak::Admin.clients.create(JSON.parse(saml_client_json))
-  end
-
-  def set_saml_config(name_identifier_format: nil, uid_attribute: nil, idp_slo_service_url: true, security: nil)
-    # Setup Zammad SAML authentication.
-    response = UserAgent.get(saml_realm_zammad_descriptor)
-    raise 'No Zammad realm descriptor found' if !response.success?
-
-    match = response.body.match(%r{<ds:X509Certificate>(?<cert>.+)</ds:X509Certificate>})
-    raise 'No X509Certificate found' if !match[:cert]
-
-    auth_saml_credentials =
-      {
-        display_name:           'SAML',
-        idp_sso_target_url:     "#{saml_base_url}/realms/zammad/protocol/saml",
-        idp_cert:               "-----BEGIN CERTIFICATE-----\n#{match[:cert]}\n-----END CERTIFICATE-----",
-        name_identifier_format: name_identifier_format || 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
-      }
-    auth_saml_credentials[:idp_slo_service_url] = "#{saml_base_url}/realms/zammad/protocol/saml" if idp_slo_service_url
-    auth_saml_credentials[:uid_attribute] = uid_attribute if uid_attribute
-
-    if security.present?
-      auth_saml_credentials[:security] = 'on'
-      auth_saml_credentials[:certificate] = "-----BEGIN CERTIFICATE-----\n#{security[:cert]}\n-----END CERTIFICATE-----"
-      auth_saml_credentials[:private_key] = "-----BEGIN RSA PRIVATE KEY-----\n#{security[:key]}\n-----END RSA PRIVATE KEY-----" # gitleaks:allow
-      auth_saml_credentials[:private_key_secret] = ''
-    end
-
-    # Disable setting validation. We have an explicit test for this.
-    setting = Setting.find_by(name: 'auth_saml_credentials')
-    setting.state_current = { value: auth_saml_credentials }
-    setting.save!(validate: false)
-
-    Setting.set('auth_saml', true)
+    saml_configure_keycloak(zammad_saml_metadata:, saml_client_json:)
   end
 
   # Shared_examples does not work.
@@ -76,24 +28,11 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
       find('.icon-saml').click
     end
 
-    login_saml_keycloak
+    saml_login_keycloak
 
     check_logged_in(app: app)
   end
 
-  def login_saml_keycloak
-    find_by_id('kc-form')
-    expect(page).to have_current_path(%r{/realms/zammad/protocol/saml\?SAMLRequest=.+})
-    expect(page).to have_css('#kc-form-login')
-
-    within '#kc-form-login' do
-      fill_in 'username', with: 'john.doe'
-      fill_in 'password', with: 'test'
-
-      click_on 'Sign In'
-    end
-  end
-
   def check_logged_in(app: 'desktop')
     find_by_id('app')
 
@@ -117,7 +56,7 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
   # TODO: Should be replaced with tests for the new desktop-view (or the test in general should be removed outside of selenium).
   describe 'SP login and SP logout' do
     before do
-      set_saml_config(security: security)
+      saml_configure_zammad(saml_base_url:, saml_realm_zammad_descriptor:, security:)
     end
 
     let(:security) { nil }
@@ -126,14 +65,12 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
       login_saml
 
       visit saml_realm_zammad_accounts
-      expect(page).to have_css('#landingSignOutButton')
-      find_by_id('landingWelcomeMessage')
+      expect(page).to have_text('John Doe')
 
       logout_saml
 
       visit saml_realm_zammad_accounts
-      expect(page).to have_no_css('#landingSignOutButton')
-      find_by_id('landingWelcomeMessage')
+      expect(page).to have_text('Sign in')
     end
 
     context 'with client signature required and encrypted assertions enabled' do
@@ -165,8 +102,8 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
         cert = pem
 
         pem = key.to_pem
-        pem.gsub!('-----BEGIN RSA PRIVATE KEY-----', '')
-        pem.gsub!('-----END RSA PRIVATE KEY-----', '')
+        pem.gsub!('-----BEGIN RSA PRIVATE KEY-----', '') # gitleaks:allow
+        pem.gsub!('-----END RSA PRIVATE KEY-----', '') # gitleaks:allow
         pem.delete!("\n").strip!
         key = pem
 
@@ -187,14 +124,12 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
         login_saml
 
         visit saml_realm_zammad_accounts
-        expect(page).to have_css('#landingSignOutButton')
-        find_by_id('landingWelcomeMessage')
+        expect(page).to have_text('John Doe')
 
         logout_saml
 
         visit saml_realm_zammad_accounts
-        expect(page).to have_no_css('#landingSignOutButton')
-        find_by_id('landingWelcomeMessage')
+        expect(page).to have_text('Sign in')
       end
 
     end
@@ -202,7 +137,7 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
 
   describe 'SP login and IDP logout' do
     before do
-      set_saml_config
+      saml_configure_zammad(saml_base_url:, saml_realm_zammad_descriptor:)
     end
 
     it 'is successful' do
@@ -210,8 +145,8 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
 
       visit saml_realm_zammad_accounts
 
-      find_by_id('landingWelcomeMessage')
-      find('#landingSignOutButton').click
+      click_on 'John Doe'
+      find('a', text: 'Sign out').click
 
       visit '/'
       expect(page).to have_current_route('login')
@@ -221,7 +156,7 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
 
   describe "use custom user attribute 'uid' as uid_attribute" do
     before do
-      set_saml_config(uid_attribute: 'uid')
+      saml_configure_zammad(saml_base_url:, saml_realm_zammad_descriptor:, uid_attribute: 'uid')
     end
 
     it 'is successful' do
@@ -236,7 +171,7 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
 
   describe 'use unspecified (IDP provided) name identifier' do
     before do
-      set_saml_config(name_identifier_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified')
+      saml_configure_zammad(saml_base_url:, saml_realm_zammad_descriptor:, name_identifier_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified')
     end
 
     it 'is successful' do
@@ -251,7 +186,7 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
 
   describe 'SAML logout without IDP SLO service URL' do
     before do
-      set_saml_config(idp_slo_service_url: false)
+      saml_configure_zammad(saml_base_url:, saml_realm_zammad_descriptor:, idp_slo_service_url: false)
     end
 
     it 'is successful' do
@@ -263,7 +198,7 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
       logout_saml
 
       visit saml_realm_zammad_accounts
-      expect(page).to have_css('#landingSignOutButton')
+      expect(page).to have_text('John Doe')
     end
   end
 
@@ -274,21 +209,21 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
 
     context 'when login is tested' do
       before do
-        set_saml_config
+        saml_configure_zammad(saml_base_url:, saml_realm_zammad_descriptor:)
       end
 
       it 'is successful' do
         login_saml(app: 'mobile')
 
         visit saml_realm_zammad_accounts
-        find('#landingMobileKebabButton').click
-        expect(page).to have_css('#landingSignOutLink')
+        click_on 'Actions'
+        expect(page).to have_text('Sign out')
       end
     end
 
     context 'when logout is tested' do
       before do
-        set_saml_config
+        saml_configure_zammad(saml_base_url:, saml_realm_zammad_descriptor:)
       end
 
       it 'is successful' do
@@ -302,9 +237,7 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
         end
 
         visit saml_realm_zammad_accounts
-        find('#landingMobileKebabButton').click
-        expect(page).to have_no_css('#landingSignOutLink')
-        find_by_id('landingWelcomeMessage')
+        expect(page).to have_text('Sign in')
       end
     end
 
@@ -313,21 +246,21 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
         Setting.set('auth_third_party_auto_link_at_inital_login', true)
         create(:agent, email: 'john.doe@saml.example.com', login: 'john.doe', firstname: 'John', lastname: 'Doe')
 
-        set_saml_config
+        saml_configure_zammad(saml_base_url:, saml_realm_zammad_descriptor:)
       end
 
       it 'is successful' do
         login_saml(app: 'mobile')
 
         visit saml_realm_zammad_accounts
-        find('#landingMobileKebabButton').click
-        expect(page).to have_css('#landingSignOutLink')
+        click_on 'Actions'
+        expect(page).to have_text('Sign out')
       end
     end
 
     context 'when logout is tested without IDP SLO service URL' do
       before do
-        set_saml_config(idp_slo_service_url: false)
+        saml_configure_zammad(saml_base_url:, saml_realm_zammad_descriptor:, idp_slo_service_url: false)
       end
 
       it 'is successful' do
@@ -341,8 +274,8 @@ RSpec.describe 'SAML Authentication', authenticated_as: false, integration: true
         end
 
         visit saml_realm_zammad_accounts
-        find('#landingMobileKebabButton').click
-        expect(page).to have_css('#landingSignOutLink')
+        click_on 'Actions'
+        expect(page).to have_text('Sign out')
       end
     end