Add keycloak OIDC based federation tests

This patch adds federation tests for horizon and
cover the scenario to make sure authentication
via an external IDP like keycloak works.

Note: The federation based tests are skipped
for now as we don't have a mechanism upstream
to run these in a job and will be skipped until
such job is developed

Change-Id: I5afd1c05a74d905b6c732df2659c9e5d155aa633
This commit is contained in:
Ashish Gupta
2025-06-18 22:01:14 +05:30
parent d2393c333d
commit b6e47527cc
5 changed files with 116 additions and 1 deletions

View File

@@ -242,6 +242,20 @@ ThemeGroup = [
help='Default xpath for second dropdown of browse panel'),
]
OIDCGroup = [
cfg.StrOpt('keycloak_test_user1_username',
default='kctestuser1',
help='Username to use for keycloak test user 1'),
cfg.StrOpt('keycloak_test_user1_password',
default='nomoresecrets1',
help='Password to use for keycloak test user 1.',
secret=True),
cfg.StrOpt('keycloak_test_user_home_project',
default='SSOproject',
help='Project to keep all objects belonging to a '
'regular keycloak user.'),
]
def _get_config_files():
conf_dir = os.path.join(
@@ -270,6 +284,7 @@ def get_config():
cfg.CONF.register_opts(PluginGroup, group="plugin")
cfg.CONF.register_opts(VolumeGroup, group="volume")
cfg.CONF.register_opts(ThemeGroup, group="theme")
cfg.CONF.register_opts(OIDCGroup, group="OIDC")
return cfg.CONF
@@ -288,4 +303,5 @@ def list_opts():
("plugin", PluginGroup),
("volume", VolumeGroup),
("theme", ThemeGroup),
("OIDC", OIDCGroup),
]

View File

@@ -133,3 +133,10 @@ browse_left_panel_sec=None,compute,compute,compute,compute,compute,volumes,volum
b_l_p_sec_line_xpath=.//*[@id="sidebar-accordion-{main_panel}"]
b_l_p_sec_line_req_btn=.//a[@data-target="#sidebar-accordion-{main_panel}-{sec_panel}"]
b_l_p_sidebar_xpath=.//*[@id="sidebar-accordion-{main_panel}-{sec_panel}"]
#Parameters for keycloak test user
[OIDC]
#OIDC parameters for keycloak login
keycloak_test_user1_username=kctestuser1
keycloak_test_user1_password=nomoresecrets1
keycloak_test_user_home_project=SSOproject

View File

@@ -18,6 +18,7 @@ from threading import Thread
import time
import pytest
from selenium.webdriver.support.ui import Select
import xvfbwrapper
from horizon.test import webdriver
@@ -45,6 +46,13 @@ class Session:
config.identity.admin_home_project,
),
}
self.oidc_credentials = {
'user': (
config.OIDC.keycloak_test_user1_username,
config.OIDC.keycloak_test_user1_password,
config.OIDC.keycloak_test_user_home_project,
),
}
self.project_name_xpath = config.theme.project_name_xpath
self.logout_url = '/'.join((
config.dashboard.dashboard_url,
@@ -78,6 +86,26 @@ class Session:
self.current_project = self.driver.find_element_by_xpath(
self.project_name_xpath).text
def login_oidc(self, user, project=None):
# Keycloak/OIDC login
username, password, home_project = self.oidc_credentials[user]
if project is None:
project = home_project
self.driver.get(self.logout_url)
select_auth = self.driver.find_element_by_id('id_auth_type')
select_auth.click()
select_opt = Select(select_auth)
select_opt.select_by_visible_text('OpenID Connect')
button = self.driver.find_element_by_css_selector(
'.btn-primary')
button.click()
keycloak_user_field = self.driver.find_element_by_id('username')
keycloak_user_field.send_keys(username)
keycloak_pass_field = self.driver.find_element_by_id('password')
keycloak_pass_field.send_keys(password)
kc_login_button = self.driver.find_element_by_id('kc-login')
kc_login_button.click()
@pytest.fixture(scope='session')
def login(driver, config):

View File

@@ -0,0 +1,64 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pytest
from selenium.common.exceptions import NoSuchElementException
from openstack_dashboard.test.selenium.conftest import Session
@pytest.fixture(scope='session')
def login_oidc(driver, config):
session = Session(driver, config)
return session.login_oidc
def test_federation_keystone_user_login(login, driver, config):
login('user')
try:
driver.find_element_by_xpath(
config.theme.user_name_xpath)
assert True
except NoSuchElementException:
assert False
def test_federation_keystone_admin_login(login, driver, config):
login('admin')
try:
driver.find_element_by_xpath(
config.theme.user_name_xpath)
assert True
except NoSuchElementException:
assert False
def test_federation_keycloak_test_user_login(login_oidc, driver, config):
login_oidc('user')
project = config.OIDC.keycloak_test_user_home_project
username = config.OIDC.keycloak_test_user1_username
# Check that expected project exists
try:
project_element = driver.find_element_by_xpath(
config.theme.project_name_xpath)
project_element.find_element_by_xpath(
f'.//*[normalize-space()="{project}"]')
except NoSuchElementException:
assert False, f"Project name '{project}' isn't found"
# Check that expected username exists
try:
username_element = driver.find_element_by_xpath(
config.theme.user_name_xpath)
username_element.find_element_by_xpath(
f'.//*[normalize-space()="{username}"]')
except NoSuchElementException:
assert False, f"Username '{username}' isn't found"

View File

@@ -98,7 +98,7 @@ setenv =
SELENIUM_HEADLESS=1
commands =
oslo-config-generator --namespace openstack_dashboard_integration_tests
pytest -v --junitxml="{toxinidir}/test_reports/integration_pytest_results.xml" --html="{toxinidir}/test_reports/integration_pytest_results.html" --self-contained-html {posargs:{toxinidir}/openstack_dashboard/test/selenium/integration}
pytest -v -k "not federation" --junitxml="{toxinidir}/test_reports/integration_pytest_results.xml" --html="{toxinidir}/test_reports/integration_pytest_results.html" --self-contained-html {posargs:{toxinidir}/openstack_dashboard/test/selenium/integration}
[testenv:ui-pytest]
# Run pytest ui tests only