From e743d2fd26fbe53baf7f0584405212e8683d5649 Mon Sep 17 00:00:00 2001 From: Vitaliy Zakaznikov Date: Thu, 22 Oct 2020 19:14:58 -0400 Subject: [PATCH] * Fixing tests in the tests/testflows/ldap/external_user_directory/tests/authentications.py suite * Moving TestFlows runs to use classic output format for stdout * Moving to TestFlows 1.6.57 * Updating LDAP test code styling --- docker/test/testflows/runner/Dockerfile | 4 +- .../authentication/tests/authentications.py | 62 ++- .../ldap/authentication/tests/common.py | 2 + .../ldap/authentication/tests/connections.py | 17 +- .../authentication/tests/multiple_servers.py | 5 +- .../authentication/tests/server_config.py | 1 + .../tests/authentications.py | 507 ++++++++++-------- .../external_user_directory/tests/common.py | 37 +- 8 files changed, 381 insertions(+), 254 deletions(-) diff --git a/docker/test/testflows/runner/Dockerfile b/docker/test/testflows/runner/Dockerfile index ed49743319c..9565e39598c 100644 --- a/docker/test/testflows/runner/Dockerfile +++ b/docker/test/testflows/runner/Dockerfile @@ -35,7 +35,7 @@ RUN apt-get update \ ENV TZ=Europe/Moscow RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone -RUN pip3 install urllib3 testflows==1.6.48 docker-compose docker dicttoxml kazoo tzlocal +RUN pip3 install urllib3 testflows==1.6.57 docker-compose docker dicttoxml kazoo tzlocal ENV DOCKER_CHANNEL stable ENV DOCKER_VERSION 17.09.1-ce @@ -72,5 +72,5 @@ RUN set -x \ VOLUME /var/lib/docker EXPOSE 2375 ENTRYPOINT ["dockerd-entrypoint.sh"] -CMD ["sh", "-c", "python3 regression.py --no-color --local --clickhouse-binary-path ${CLICKHOUSE_TESTS_SERVER_BIN_PATH} --log test.log ${TESTFLOWS_OPTS}; cat test.log | tfs report results --format json > results.json"] +CMD ["sh", "-c", "python3 regression.py --no-color -o classic --local --clickhouse-binary-path ${CLICKHOUSE_TESTS_SERVER_BIN_PATH} --log test.log ${TESTFLOWS_OPTS}; cat test.log | tfs report results --format json > results.json"] diff --git a/tests/testflows/ldap/authentication/tests/authentications.py b/tests/testflows/ldap/authentication/tests/authentications.py index 1b21dce7cc1..a64a37ed686 100644 --- a/tests/testflows/ldap/authentication/tests/authentications.py +++ b/tests/testflows/ldap/authentication/tests/authentications.py @@ -28,6 +28,8 @@ servers = { @TestStep(When) @Name("I login as {username} and execute query") def login_and_execute_query(self, username, password, exitcode=None, message=None, steps=True): + """Execute query as some user. + """ self.context.node.query("SELECT 1", settings=[("user", username), ("password", password)], exitcode=exitcode or 0, @@ -35,7 +37,8 @@ def login_and_execute_query(self, username, password, exitcode=None, message=Non @TestScenario def add_user_to_ldap_and_login(self, server, user=None, ch_user=None, login=None, exitcode=None, message=None, rbac=False): - """Add user to LDAP and ClickHouse and then try to login.""" + """Add user to LDAP and ClickHouse and then try to login. + """ self.context.ldap_node = self.context.cluster.node(server) if ch_user is None: @@ -60,7 +63,8 @@ def add_user_to_ldap_and_login(self, server, user=None, ch_user=None, login=None RQ_SRS_007_LDAP_Authentication_Parallel_ValidAndInvalid("1.0") ) def parallel_login(self, server, user_count=10, timeout=200, rbac=False): - """Check that login of valid and invalid LDAP authenticated users works in parallel.""" + """Check that login of valid and invalid LDAP authenticated users works in parallel. + """ self.context.ldap_node = self.context.cluster.node(server) user = None @@ -114,7 +118,8 @@ def parallel_login(self, server, user_count=10, timeout=200, rbac=False): RQ_SRS_007_LDAP_Authentication_Invalid_DeletedUser("1.0") ) def login_after_user_is_deleted_from_ldap(self, server, rbac=False): - """Check that login fails after user is deleted from LDAP.""" + """Check that login fails after user is deleted from LDAP. + """ self.context.ldap_node = self.context.cluster.node(server) user = None @@ -146,7 +151,8 @@ def login_after_user_is_deleted_from_ldap(self, server, rbac=False): RQ_SRS_007_LDAP_Authentication_PasswordChanged("1.0") ) def login_after_user_password_changed_in_ldap(self, server, rbac=False): - """Check that login fails after user password is changed in LDAP.""" + """Check that login fails after user password is changed in LDAP. + """ self.context.ldap_node = self.context.cluster.node(server) user = None @@ -182,7 +188,8 @@ def login_after_user_password_changed_in_ldap(self, server, rbac=False): RQ_SRS_007_LDAP_Authentication_UsernameChanged("1.0") ) def login_after_user_cn_changed_in_ldap(self, server, rbac=False): - """Check that login fails after user cn is changed in LDAP.""" + """Check that login fails after user cn is changed in LDAP. + """ self.context.ldap_node = self.context.cluster.node(server) user = None new_user = None @@ -215,7 +222,8 @@ def login_after_user_cn_changed_in_ldap(self, server, rbac=False): RQ_SRS_007_LDAP_Authentication_LDAPServerRestart("1.0") ) def login_after_ldap_server_is_restarted(self, server, timeout=60, rbac=False): - """Check that login succeeds after LDAP server is restarted.""" + """Check that login succeeds after LDAP server is restarted. + """ self.context.ldap_node = self.context.cluster.node(server) user = None @@ -250,7 +258,8 @@ def login_after_ldap_server_is_restarted(self, server, timeout=60, rbac=False): RQ_SRS_007_LDAP_Authentication_ClickHouseServerRestart("1.0") ) def login_after_clickhouse_server_is_restarted(self, server, timeout=60, rbac=False): - """Check that login succeeds after ClickHouse server is restarted.""" + """Check that login succeeds after ClickHouse server is restarted. + """ self.context.ldap_node = self.context.cluster.node(server) user = None @@ -285,7 +294,8 @@ def login_after_clickhouse_server_is_restarted(self, server, timeout=60, rbac=Fa RQ_SRS_007_LDAP_Authentication_Password_Empty("1.0") ) def valid_username_with_valid_empty_password(self, server, rbac=False): - """Check that we can't login using valid username that has empty password.""" + """Check that we can't login using valid username that has empty password. + """ user = {"cn": "empty_password", "userpassword": ""} exitcode = 4 message = f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" @@ -298,7 +308,8 @@ def valid_username_with_valid_empty_password(self, server, rbac=False): RQ_SRS_007_LDAP_Authentication_Password_Empty("1.0") ) def valid_username_and_invalid_empty_password(self, server, rbac=False): - """Check that we can't login using valid username but invalid empty password.""" + """Check that we can't login using valid username but invalid empty password. + """ username = "user_non_empty_password" user = {"cn": username, "userpassword": username} login = {"password": ""} @@ -313,7 +324,8 @@ def valid_username_and_invalid_empty_password(self, server, rbac=False): RQ_SRS_007_LDAP_Authentication_Valid("1.0") ) def valid_username_and_password(self, server, rbac=False): - """Check that we can login using valid username and password.""" + """Check that we can login using valid username and password. + """ username = "valid_username_and_password" user = {"cn": username, "userpassword": username} @@ -326,7 +338,8 @@ def valid_username_and_password(self, server, rbac=False): ) def valid_username_and_password_invalid_server(self, server=None, rbac=False): """Check that we can't login using valid username and valid - password but for a different server.""" + password but for a different server. + """ self.context.ldap_node = self.context.cluster.node("openldap1") user = {"username": "user2", "userpassword": "user2", "server": "openldap1"} @@ -344,7 +357,8 @@ def valid_username_and_password_invalid_server(self, server=None, rbac=False): RQ_SRS_007_LDAP_Configuration_User_Name_Long("1.0") ) def valid_long_username_and_short_password(self, server, rbac=False): - """Check that we can login using valid very long username and short password.""" + """Check that we can login using valid very long username and short password. + """ username = "long_username_12345678901234567890123456789012345678901234567890123456789012345678901234567890" user = {"cn": username, "userpassword": "long_username"} @@ -355,7 +369,8 @@ def valid_long_username_and_short_password(self, server, rbac=False): RQ_SRS_007_LDAP_Authentication_Invalid("1.0") ) def invalid_long_username_and_valid_short_password(self, server, rbac=False): - """Check that we can't login using slightly invalid long username but valid password.""" + """Check that we can't login using slightly invalid long username but valid password. + """ username = "long_username_12345678901234567890123456789012345678901234567890123456789012345678901234567890" user = {"cn": username, "userpassword": "long_username"} login = {"username": f"{username}?"} @@ -371,7 +386,8 @@ def invalid_long_username_and_valid_short_password(self, server, rbac=False): RQ_SRS_007_LDAP_Authentication_Password_Long("1.0") ) def valid_short_username_and_long_password(self, server, rbac=False): - """Check that we can login using valid short username with very long password.""" + """Check that we can login using valid short username with very long password. + """ username = "long_password" user = {"cn": username, "userpassword": "long_password_12345678901234567890123456789012345678901234567890123456789012345678901234567890"} add_user_to_ldap_and_login(user=user, server=server, rbac=rbac) @@ -381,7 +397,8 @@ def valid_short_username_and_long_password(self, server, rbac=False): RQ_SRS_007_LDAP_Authentication_Invalid("1.0") ) def valid_short_username_and_invalid_long_password(self, server, rbac=False): - """Check that we can't login using valid short username and invalid long password.""" + """Check that we can't login using valid short username and invalid long password. + """ username = "long_password" user = {"cn": username, "userpassword": "long_password_12345678901234567890123456789012345678901234567890123456789012345678901234567890"} login = {"password": user["userpassword"] + "1"} @@ -396,7 +413,8 @@ def valid_short_username_and_invalid_long_password(self, server, rbac=False): RQ_SRS_007_LDAP_Authentication_Invalid("1.0") ) def valid_username_and_invalid_password(self, server, rbac=False): - """Check that we can't login using valid username and invalid password.""" + """Check that we can't login using valid username and invalid password. + """ username = "valid_username_and_invalid_password" user = {"cn": username, "userpassword": username} login = {"password": user["userpassword"] + "1"} @@ -411,7 +429,8 @@ def valid_username_and_invalid_password(self, server, rbac=False): RQ_SRS_007_LDAP_Authentication_Invalid("1.0") ) def invalid_username_and_valid_password(self, server, rbac=False): - """Check that we can't login using slightly invalid username but valid password.""" + """Check that we can't login using slightly invalid username but valid password. + """ username = "invalid_username_and_valid_password" user = {"cn": username, "userpassword": username} login = {"username": user["cn"] + "1"} @@ -428,7 +447,8 @@ def invalid_username_and_valid_password(self, server, rbac=False): RQ_SRS_007_LDAP_Configuration_User_Name_UTF8("1.0") ) def valid_utf8_username_and_ascii_password(self, server, rbac=False): - """Check that we can login using valid utf-8 username with ascii password.""" + """Check that we can login using valid utf-8 username with ascii password. + """ username = "utf8_username_Gãńdåłf_Thê_Gręât" user = {"cn": username, "userpassword": "utf8_username"} @@ -440,7 +460,8 @@ def valid_utf8_username_and_ascii_password(self, server, rbac=False): RQ_SRS_007_LDAP_Authentication_Password_UTF8("1.0") ) def valid_ascii_username_and_utf8_password(self, server, rbac=False): - """Check that we can login using valid ascii username with utf-8 password.""" + """Check that we can login using valid ascii username with utf-8 password. + """ username = "utf8_password" user = {"cn": username, "userpassword": "utf8_password_Gãńdåłf_Thê_Gręât"} @@ -449,7 +470,8 @@ def valid_ascii_username_and_utf8_password(self, server, rbac=False): @TestScenario def empty_username_and_empty_password(self, server=None, rbac=False): """Check that we can login using empty username and empty password as - it will use the default user and that has an empty password.""" + it will use the default user and that has an empty password. + """ login_and_execute_query(username="", password="") @TestOutline(Feature) diff --git a/tests/testflows/ldap/authentication/tests/common.py b/tests/testflows/ldap/authentication/tests/common.py index cf5cfc1d573..4e3d1e16647 100644 --- a/tests/testflows/ldap/authentication/tests/common.py +++ b/tests/testflows/ldap/authentication/tests/common.py @@ -95,6 +95,8 @@ def add_config(config, timeout=20, restart=False): if exitcode == 0: break time.sleep(1) + if settings.debug: + node.command(f"cat /var/lib/clickhouse/preprocessed_configs/{config.preprocessed_name}") assert exitcode == 0, error() def wait_for_config_to_be_loaded(): diff --git a/tests/testflows/ldap/authentication/tests/connections.py b/tests/testflows/ldap/authentication/tests/connections.py index f16f6c29b0e..dfb920181e1 100644 --- a/tests/testflows/ldap/authentication/tests/connections.py +++ b/tests/testflows/ldap/authentication/tests/connections.py @@ -98,7 +98,8 @@ def starttls_with_custom_port(self): login(servers, *users) def tls_connection(enable_tls, tls_require_cert): - """Try to login using LDAP user authentication over a TLS connection.""" + """Try to login using LDAP user authentication over a TLS connection. + """ servers = { "openldap2": { "host": "openldap2", @@ -152,7 +153,8 @@ def tls(self): RQ_SRS_007_LDAP_Configuration_Server_EnableTLS_Options_Default("1.0") ) def tls_enable_tls_default_yes(self): - """Check that the default value for the `enable_tls` is set to `yes`.""" + """Check that the default value for the `enable_tls` is set to `yes`. + """ servers = { "openldap2": { "host": "openldap2", @@ -171,7 +173,8 @@ def tls_enable_tls_default_yes(self): RQ_SRS_007_LDAP_Configuration_Server_TLSRequireCert_Options_Default("1.0") ) def tls_require_cert_default_demand(self): - """Check that the default value for the `tls_require_cert` is set to `demand`.""" + """Check that the default value for the `tls_require_cert` is set to `demand`. + """ servers = { "openldap2": { "host": "openldap2", @@ -210,7 +213,8 @@ def starttls(self): RQ_SRS_007_LDAP_Configuration_Server_TLSCipherSuite("1.0") ) def tls_cipher_suite(self): - """Check that `tls_cipher_suite` parameter can be used specify allowed cipher suites.""" + """Check that `tls_cipher_suite` parameter can be used specify allowed cipher suites. + """ servers = { "openldap4": { "host": "openldap4", @@ -241,7 +245,8 @@ def tls_cipher_suite(self): ]) def tls_minimum_protocol_version(self, version, exitcode, message): """Check that `tls_minimum_protocol_version` parameter can be used specify - to specify the minimum protocol version of SSL/TLS.""" + to specify the minimum protocol version of SSL/TLS. + """ servers = { "openldap4": { @@ -278,6 +283,8 @@ def tls_minimum_protocol_version(self, version, exitcode, message): @TestFeature @Name("connection protocols") def feature(self, node="clickhouse1"): + """Check different LDAP connection protocols. + """ self.context.node = self.context.cluster.node(node) for scenario in loads(current_module(), Scenario): diff --git a/tests/testflows/ldap/authentication/tests/multiple_servers.py b/tests/testflows/ldap/authentication/tests/multiple_servers.py index 6e906023b0a..c4317187b74 100644 --- a/tests/testflows/ldap/authentication/tests/multiple_servers.py +++ b/tests/testflows/ldap/authentication/tests/multiple_servers.py @@ -14,6 +14,7 @@ def scenario(self, node="clickhouse1"): authenticate users. """ self.context.node = self.context.cluster.node(node) + servers = { "openldap1": { "host": "openldap1", @@ -35,4 +36,6 @@ def scenario(self, node="clickhouse1"): {"server": "openldap1", "username": "user1", "password": "user1", "login": True}, {"server": "openldap2", "username": "user2", "password": "user2", "login": True} ] - login(servers, *users) + + with When("I add multiple LDAP servers and users that use different servers and try to login"): + login(servers, *users) diff --git a/tests/testflows/ldap/authentication/tests/server_config.py b/tests/testflows/ldap/authentication/tests/server_config.py index 80f2a496b0e..f62fda0bbf7 100644 --- a/tests/testflows/ldap/authentication/tests/server_config.py +++ b/tests/testflows/ldap/authentication/tests/server_config.py @@ -267,5 +267,6 @@ def feature(self, node="clickhouse1"): """Check that LDAP server configuration. """ self.context.node = self.context.cluster.node(node) + for scenario in loads(current_module(), Scenario): scenario() diff --git a/tests/testflows/ldap/external_user_directory/tests/authentications.py b/tests/testflows/ldap/external_user_directory/tests/authentications.py index bf5a788c4d5..9b216e7dd30 100644 --- a/tests/testflows/ldap/external_user_directory/tests/authentications.py +++ b/tests/testflows/ldap/external_user_directory/tests/authentications.py @@ -28,7 +28,8 @@ servers = { @TestOutline def add_user_to_ldap_and_login(self, server, user=None, ch_user=None, login=None, exitcode=None, message=None): - """Add user to LDAP and ClickHouse and then try to login.""" + """Add user to LDAP and ClickHouse and then try to login. + """ self.context.ldap_node = self.context.cluster.node(server) if ch_user is None: @@ -91,23 +92,25 @@ def parallel_login(self, server, user_count=10, timeout=200): with Given("a group of LDAP users"): users = [{"cn": f"parallel_user{i}", "userpassword": randomword(20)} for i in range(user_count)] - with ldap_users(*users): - tasks = [] - try: - with When("users try to login in parallel", description=""" - * with valid username and password - * with invalid username and valid password - * with valid username and invalid password - """): - p = Pool(15) - for i in range(25): - tasks.append(p.apply_async(login_with_valid_username_and_password, (users, i, 50,))) - tasks.append(p.apply_async(login_with_valid_username_and_invalid_password, (users, i, 50,))) - tasks.append(p.apply_async(login_with_invalid_username_and_valid_password, (users, i, 50,))) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + with ldap_users(*users): + tasks = [] + try: + with When("users try to login in parallel", description=""" + * with valid username and password + * with invalid username and valid password + * with valid username and invalid password + """): + p = Pool(15) + for i in range(25): + tasks.append(p.apply_async(login_with_valid_username_and_password, (users, i, 50,))) + tasks.append(p.apply_async(login_with_valid_username_and_invalid_password, (users, i, 50,))) + tasks.append(p.apply_async(login_with_invalid_username_and_valid_password, (users, i, 50,))) - finally: - with Then("it should work"): - join(tasks, timeout) + finally: + with Then("it should work"): + join(tasks, timeout) @TestScenario @Requirements( @@ -124,34 +127,36 @@ def parallel_login_with_the_same_user(self, server, timeout=200): with Given("only one LDAP user"): users = [{"cn": f"parallel_user1", "userpassword": randomword(20)}] - with ldap_users(*users): - tasks = [] - try: - with When("the same user tries to login in parallel", description=""" - * with valid username and password - * with invalid username and valid password - * with valid username and invalid password - """): - p = Pool(15) - for i in range(25): - tasks.append(p.apply_async(login_with_valid_username_and_password, (users, i, 50,))) - tasks.append(p.apply_async(login_with_valid_username_and_invalid_password, (users, i, 50,))) - tasks.append(p.apply_async(login_with_invalid_username_and_valid_password, (users, i, 50,))) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + with ldap_users(*users): + tasks = [] + try: + with When("the same user tries to login in parallel", description=""" + * with valid username and password + * with invalid username and valid password + * with valid username and invalid password + """): + p = Pool(15) + for i in range(25): + tasks.append(p.apply_async(login_with_valid_username_and_password, (users, i, 50,))) + tasks.append(p.apply_async(login_with_valid_username_and_invalid_password, (users, i, 50,))) + tasks.append(p.apply_async(login_with_invalid_username_and_valid_password, (users, i, 50,))) - finally: - with Then("it should work"): - join(tasks, timeout) + finally: + with Then("it should work"): + join(tasks, timeout) @TestScenario def login_after_ldap_external_user_directory_is_removed(self, server): """Check that ClickHouse stops authenticating LDAP users after LDAP external user directory is removed. """ - with When("I attempt to login after LDAP external user directory is added"): + with When("I login after LDAP external user directory is added"): with ldap_external_user_directory(server="openldap2", roles=[], restart=True): login_and_execute_query(username="user2", password="user2") - with When("I attempt to login after LDAP external user directory is removed"): + with And("I attempt to login after LDAP external user directory is removed"): exitcode = 4 message = f"DB::Exception: user2: Authentication failed: password is incorrect or there is no user with such name" login_and_execute_query(username="user2", password="user2", exitcode=exitcode, message=message) @@ -318,29 +323,34 @@ def parallel_login_with_rbac_users(self, server, user_count=10, timeout=200): users = [{"cn": f"parallel_user{i}", "userpassword": randomword(20)} for i in range(user_count)] - with rbac_users(*users): - tasks = [] - try: - with When("I login in parallel"): - p = Pool(15) - for i in range(25): - tasks.append(p.apply_async(login_with_valid_username_and_password, (users, i, 50,))) - tasks.append(p.apply_async(login_with_valid_username_and_invalid_password, (users, i, 50,))) - tasks.append(p.apply_async(login_with_invalid_username_and_valid_password, (users, i, 50,))) - finally: - with Then("it should work"): - join(tasks, timeout) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + with rbac_users(*users): + tasks = [] + try: + with When("I login in parallel"): + p = Pool(15) + for i in range(25): + tasks.append(p.apply_async(login_with_valid_username_and_password, (users, i, 50,))) + tasks.append(p.apply_async(login_with_valid_username_and_invalid_password, (users, i, 50,))) + tasks.append(p.apply_async(login_with_invalid_username_and_valid_password, (users, i, 50,))) + finally: + with Then("it should work"): + join(tasks, timeout) @TestScenario @Requirements( RQ_SRS_009_LDAP_ExternalUserDirectory_Users_Authentication_NewUsers("1.0") ) def login_after_user_is_added_to_ldap(self, server): - """Check that user can login as soon as it is added to LDAP.""" + """Check that user can login as soon as it is added to LDAP. + """ user = {"cn": "myuser", "userpassword": "myuser"} - with When(f"I add user to LDAP and try to login"): - add_user_to_ldap_and_login(user=user, server=server) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + with When(f"I add user to LDAP and try to login"): + add_user_to_ldap_and_login(user=user, server=server) @TestScenario @Requirements( @@ -348,29 +358,32 @@ def login_after_user_is_added_to_ldap(self, server): RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_DeletedUsers("1.0") ) def login_after_user_is_deleted_from_ldap(self, server): - """Check that login fails after user is deleted from LDAP.""" + """Check that login fails after user is deleted from LDAP. + """ self.context.ldap_node = self.context.cluster.node(server) user = None - try: - with Given(f"I add user to LDAP"): - user = {"cn": "myuser", "userpassword": "myuser"} - user = add_user_to_ldap(**user) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + try: + with Given(f"I add user to LDAP"): + user = {"cn": "myuser", "userpassword": "myuser"} + user = add_user_to_ldap(**user) - login_and_execute_query(username=user["cn"], password=user["userpassword"]) + login_and_execute_query(username=user["cn"], password=user["userpassword"]) - with When("I delete this user from LDAP"): - delete_user_from_ldap(user) + with When("I delete this user from LDAP"): + delete_user_from_ldap(user) - with Then("when I try to login again it should fail"): - login_and_execute_query(username=user["cn"], password=user["userpassword"], - exitcode=4, - message=f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" - ) - finally: - with Finally("I make sure LDAP user is deleted"): - if user is not None: - delete_user_from_ldap(user, exitcode=None) + with Then("when I try to login again it should fail"): + login_and_execute_query(username=user["cn"], password=user["userpassword"], + exitcode=4, + message=f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" + ) + finally: + with Finally("I make sure LDAP user is deleted"): + if user is not None: + delete_user_from_ldap(user, exitcode=None) @TestScenario @Requirements( @@ -378,33 +391,36 @@ def login_after_user_is_deleted_from_ldap(self, server): RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_PasswordChanged("1.0") ) def login_after_user_password_changed_in_ldap(self, server): - """Check that login fails after user password is changed in LDAP.""" + """Check that login fails after user password is changed in LDAP. + """ self.context.ldap_node = self.context.cluster.node(server) user = None - try: - with Given(f"I add user to LDAP"): - user = {"cn": "myuser", "userpassword": "myuser"} - user = add_user_to_ldap(**user) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + try: + with Given(f"I add user to LDAP"): + user = {"cn": "myuser", "userpassword": "myuser"} + user = add_user_to_ldap(**user) - login_and_execute_query(username=user["cn"], password=user["userpassword"]) + login_and_execute_query(username=user["cn"], password=user["userpassword"]) - with When("I change user password in LDAP"): - change_user_password_in_ldap(user, "newpassword") + with When("I change user password in LDAP"): + change_user_password_in_ldap(user, "newpassword") - with Then("when I try to login again it should fail"): - login_and_execute_query(username=user["cn"], password=user["userpassword"], - exitcode=4, - message=f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" - ) + with Then("when I try to login again it should fail"): + login_and_execute_query(username=user["cn"], password=user["userpassword"], + exitcode=4, + message=f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" + ) - with And("when I try to login with the new password it should work"): - login_and_execute_query(username=user["cn"], password="newpassword") + with And("when I try to login with the new password it should work"): + login_and_execute_query(username=user["cn"], password="newpassword") - finally: - with Finally("I make sure LDAP user is deleted"): - if user is not None: - delete_user_from_ldap(user, exitcode=None) + finally: + with Finally("I make sure LDAP user is deleted"): + if user is not None: + delete_user_from_ldap(user, exitcode=None) @TestScenario @Requirements( @@ -412,30 +428,33 @@ def login_after_user_password_changed_in_ldap(self, server): RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_UsernameChanged("1.0") ) def login_after_user_cn_changed_in_ldap(self, server): - """Check that login fails after user cn is changed in LDAP.""" + """Check that login fails after user cn is changed in LDAP. + """ self.context.ldap_node = self.context.cluster.node(server) user = None new_user = None - try: - with Given(f"I add user to LDAP"): - user = {"cn": "myuser", "userpassword": "myuser"} - user = add_user_to_ldap(**user) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + try: + with Given(f"I add user to LDAP"): + user = {"cn": "myuser", "userpassword": "myuser"} + user = add_user_to_ldap(**user) - login_and_execute_query(username=user["cn"], password=user["userpassword"]) + login_and_execute_query(username=user["cn"], password=user["userpassword"]) - with When("I change user password in LDAP"): - new_user = change_user_cn_in_ldap(user, "myuser2") + with When("I change user password in LDAP"): + new_user = change_user_cn_in_ldap(user, "myuser2") - with Then("when I try to login again it should fail"): - login_and_execute_query(username=user["cn"], password=user["userpassword"], - exitcode=4, - message=f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" - ) - finally: - with Finally("I make sure LDAP user is deleted"): - if new_user is not None: - delete_user_from_ldap(new_user, exitcode=None) + with Then("when I try to login again it should fail"): + login_and_execute_query(username=user["cn"], password=user["userpassword"], + exitcode=4, + message=f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" + ) + finally: + with Finally("I make sure LDAP user is deleted"): + if new_user is not None: + delete_user_from_ldap(new_user, exitcode=None) @TestScenario @Requirements( @@ -443,33 +462,36 @@ def login_after_user_cn_changed_in_ldap(self, server): RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_LDAPServerRestart("1.0") ) def login_after_ldap_server_is_restarted(self, server, timeout=60): - """Check that login succeeds after LDAP server is restarted.""" + """Check that login succeeds after LDAP server is restarted. + """ self.context.ldap_node = self.context.cluster.node(server) user = None - try: - with Given(f"I add user to LDAP"): - user = {"cn": "myuser", "userpassword": getuid()} - user = add_user_to_ldap(**user) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + try: + with Given(f"I add user to LDAP"): + user = {"cn": "myuser", "userpassword": getuid()} + user = add_user_to_ldap(**user) - login_and_execute_query(username=user["cn"], password=user["userpassword"]) + login_and_execute_query(username=user["cn"], password=user["userpassword"]) - with When("I restart LDAP server"): - self.context.ldap_node.restart() + with When("I restart LDAP server"): + self.context.ldap_node.restart() - with Then("I try to login until it works", description=f"timeout {timeout} sec"): - started = time.time() - while True: - r = self.context.node.query("SELECT 1", - settings=[("user", user["cn"]), ("password", user["userpassword"])], - no_checks=True) - if r.exitcode == 0: - break - assert time.time() - started < timeout, error(r.output) - finally: - with Finally("I make sure LDAP user is deleted"): - if user is not None: - delete_user_from_ldap(user, exitcode=None) + with Then("I try to login until it works", description=f"timeout {timeout} sec"): + started = time.time() + while True: + r = self.context.node.query("SELECT 1", + settings=[("user", user["cn"]), ("password", user["userpassword"])], + no_checks=True) + if r.exitcode == 0: + break + assert time.time() - started < timeout, error(r.output) + finally: + with Finally("I make sure LDAP user is deleted"): + if user is not None: + delete_user_from_ldap(user, exitcode=None) @TestScenario @Requirements( @@ -477,33 +499,36 @@ def login_after_ldap_server_is_restarted(self, server, timeout=60): RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_ClickHouseServerRestart("1.0") ) def login_after_clickhouse_server_is_restarted(self, server, timeout=60): - """Check that login succeeds after ClickHouse server is restarted.""" + """Check that login succeeds after ClickHouse server is restarted. + """ self.context.ldap_node = self.context.cluster.node(server) user = None - try: - with Given(f"I add user to LDAP"): - user = {"cn": "myuser", "userpassword": getuid()} - user = add_user_to_ldap(**user) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + try: + with Given(f"I add user to LDAP"): + user = {"cn": "myuser", "userpassword": getuid()} + user = add_user_to_ldap(**user) - login_and_execute_query(username=user["cn"], password=user["userpassword"]) + login_and_execute_query(username=user["cn"], password=user["userpassword"]) - with When("I restart ClickHouse server"): - self.context.node.restart() + with When("I restart ClickHouse server"): + self.context.node.restart() - with Then("I try to login until it works", description=f"timeout {timeout} sec"): - started = time.time() - while True: - r = self.context.node.query("SELECT 1", - settings=[("user", user["cn"]), ("password", user["userpassword"])], - no_checks=True) - if r.exitcode == 0: - break - assert time.time() - started < timeout, error(r.output) - finally: - with Finally("I make sure LDAP user is deleted"): - if user is not None: - delete_user_from_ldap(user, exitcode=None) + with Then("I try to login until it works", description=f"timeout {timeout} sec"): + started = time.time() + while True: + r = self.context.node.query("SELECT 1", + settings=[("user", user["cn"]), ("password", user["userpassword"])], + no_checks=True) + if r.exitcode == 0: + break + assert time.time() - started < timeout, error(r.output) + finally: + with Finally("I make sure LDAP user is deleted"): + if user is not None: + delete_user_from_ldap(user, exitcode=None) @TestScenario @Requirements( @@ -511,12 +536,15 @@ def login_after_clickhouse_server_is_restarted(self, server, timeout=60): RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Password_Empty("1.0") ) def valid_username_with_valid_empty_password(self, server): - """Check that we can't login using valid username that has empty password.""" + """Check that we can't login using valid username that has empty password. + """ user = {"cn": "empty_password", "userpassword": ""} exitcode = 4 message = f"DB::Exception: {user['cn']}: Authentication failed: password is incorrect or there is no user with such name" - add_user_to_ldap_and_login(user=user, exitcode=exitcode, message=message, server=server) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + add_user_to_ldap_and_login(user=user, exitcode=exitcode, message=message, server=server) @TestScenario @Requirements( @@ -524,41 +552,50 @@ def valid_username_with_valid_empty_password(self, server): RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Password_Empty("1.0") ) def valid_username_and_invalid_empty_password(self, server): - """Check that we can't login using valid username but invalid empty password.""" - username = "user_non_empty_password" - user = {"cn": username, "userpassword": username} - login = {"password": ""} + """Check that we can't login using valid username but invalid empty password. + """ + username = "user_non_empty_password" + user = {"cn": username, "userpassword": username} + login = {"password": ""} - exitcode = 4 - message = f"DB::Exception: {username}: Authentication failed: password is incorrect or there is no user with such name" + exitcode = 4 + message = f"DB::Exception: {username}: Authentication failed: password is incorrect or there is no user with such name" - add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server) @TestScenario @Requirements( RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Valid("1.0") ) def valid_username_and_password(self, server): - """Check that we can login using valid username and password.""" - username = "valid_username_and_password" - user = {"cn": username, "userpassword": username} + """Check that we can login using valid username and password. + """ + username = "valid_username_and_password" + user = {"cn": username, "userpassword": username} - with When(f"I add user {username} to LDAP and try to login"): - add_user_to_ldap_and_login(user=user, server=server) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + with When(f"I add user {username} to LDAP and try to login"): + add_user_to_ldap_and_login(user=user, server=server) @TestScenario @Requirements( RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Invalid("1.0") ) def valid_username_and_password_invalid_server(self, server=None): - """Check that we can't login using valid username and valid - password but for a different server.""" - self.context.ldap_node = self.context.cluster.node("openldap1") + """Check that we can't login using valid username and valid + password but for a different server. + """ + self.context.ldap_node = self.context.cluster.node("openldap1") - exitcode = 4 - message = f"DB::Exception: user2: Authentication failed: password is incorrect or there is no user with such name" + exitcode = 4 + message = f"DB::Exception: user2: Authentication failed: password is incorrect or there is no user with such name" - login_and_execute_query(username="user2", password="user2", exitcode=exitcode, message=message) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + login_and_execute_query(username="user2", password="user2", exitcode=exitcode, message=message) @TestScenario @Requirements( @@ -566,26 +603,32 @@ def valid_username_and_password_invalid_server(self, server=None): RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Username_Long("1.0"), ) def valid_long_username_and_short_password(self, server): - """Check that we can login using valid very long username and short password.""" - username = "long_username_12345678901234567890123456789012345678901234567890123456789012345678901234567890" - user = {"cn": username, "userpassword": "long_username"} + """Check that we can login using valid very long username and short password. + """ + username = "long_username_12345678901234567890123456789012345678901234567890123456789012345678901234567890" + user = {"cn": username, "userpassword": "long_username"} - add_user_to_ldap_and_login(user=user, server=server) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + add_user_to_ldap_and_login(user=user, server=server) @TestScenario @Requirements( RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Invalid("1.0") ) def invalid_long_username_and_valid_short_password(self, server): - """Check that we can't login using slightly invalid long username but valid password.""" - username = "long_username_12345678901234567890123456789012345678901234567890123456789012345678901234567890" - user = {"cn": username, "userpassword": "long_username"} - login = {"username": f"{username}?"} + """Check that we can't login using slightly invalid long username but valid password. + """ + username = "long_username_12345678901234567890123456789012345678901234567890123456789012345678901234567890" + user = {"cn": username, "userpassword": "long_username"} + login = {"username": f"{username}?"} - exitcode = 4 - message=f"DB::Exception: {login['username']}: Authentication failed: password is incorrect or there is no user with such name" + exitcode = 4 + message=f"DB::Exception: {login['username']}: Authentication failed: password is incorrect or there is no user with such name" - add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server) @TestScenario @Requirements( @@ -593,55 +636,68 @@ def invalid_long_username_and_valid_short_password(self, server): RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Password_Long("1.0") ) def valid_short_username_and_long_password(self, server): - """Check that we can login using valid short username with very long password.""" - username = "long_password" - user = {"cn": username, "userpassword": "long_password_12345678901234567890123456789012345678901234567890123456789012345678901234567890"} - add_user_to_ldap_and_login(user=user, server=server) + """Check that we can login using valid short username with very long password. + """ + username = "long_password" + user = {"cn": username, "userpassword": "long_password_12345678901234567890123456789012345678901234567890123456789012345678901234567890"} + + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + add_user_to_ldap_and_login(user=user, server=server) @TestScenario @Requirements( RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Invalid("1.0") ) def valid_short_username_and_invalid_long_password(self, server): - """Check that we can't login using valid short username and invalid long password.""" - username = "long_password" - user = {"cn": username, "userpassword": "long_password_12345678901234567890123456789012345678901234567890123456789012345678901234567890"} - login = {"password": user["userpassword"] + "1"} + """Check that we can't login using valid short username and invalid long password. + """ + username = "long_password" + user = {"cn": username, "userpassword": "long_password_12345678901234567890123456789012345678901234567890123456789012345678901234567890"} + login = {"password": user["userpassword"] + "1"} - exitcode = 4 - message=f"DB::Exception: {username}: Authentication failed: password is incorrect or there is no user with such name" + exitcode = 4 + message=f"DB::Exception: {username}: Authentication failed: password is incorrect or there is no user with such name" - add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server) @TestScenario @Requirements( RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Invalid("1.0") ) def valid_username_and_invalid_password(self, server): - """Check that we can't login using valid username and invalid password.""" - username = "valid_username_and_invalid_password" - user = {"cn": username, "userpassword": username} - login = {"password": user["userpassword"] + "1"} + """Check that we can't login using valid username and invalid password. + """ + username = "valid_username_and_invalid_password" + user = {"cn": username, "userpassword": username} + login = {"password": user["userpassword"] + "1"} - exitcode = 4 - message=f"DB::Exception: {username}: Authentication failed: password is incorrect or there is no user with such name" + exitcode = 4 + message=f"DB::Exception: {username}: Authentication failed: password is incorrect or there is no user with such name" - add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server) @TestScenario @Requirements( RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Invalid("1.0") ) def invalid_username_and_valid_password(self, server): - """Check that we can't login using slightly invalid username but valid password.""" - username = "invalid_username_and_valid_password" - user = {"cn": username, "userpassword": username} - login = {"username": user["cn"] + "1"} + """Check that we can't login using slightly invalid username but valid password. + """ + username = "invalid_username_and_valid_password" + user = {"cn": username, "userpassword": username} + login = {"username": user["cn"] + "1"} - exitcode = 4 - message=f"DB::Exception: {login['username']}: Authentication failed: password is incorrect or there is no user with such name" + exitcode = 4 + message=f"DB::Exception: {login['username']}: Authentication failed: password is incorrect or there is no user with such name" - add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + add_user_to_ldap_and_login(user=user, login=login, exitcode=exitcode, message=message, server=server) @TestScenario @Requirements( @@ -649,11 +705,14 @@ def invalid_username_and_valid_password(self, server): RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Username_UTF8("1.0") ) def valid_utf8_username_and_ascii_password(self, server): - """Check that we can login using valid utf-8 username with ascii password.""" - username = "utf8_username_Gãńdåłf_Thê_Gręât" - user = {"cn": username, "userpassword": "utf8_username"} + """Check that we can login using valid utf-8 username with ascii password. + """ + username = "utf8_username_Gãńdåłf_Thê_Gręât" + user = {"cn": username, "userpassword": "utf8_username"} - add_user_to_ldap_and_login(user=user, server=server) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + add_user_to_ldap_and_login(user=user, server=server) @TestScenario @Requirements( @@ -661,17 +720,23 @@ def valid_utf8_username_and_ascii_password(self, server): RQ_SRS_009_LDAP_ExternalUserDirectory_Authentication_Password_UTF8("1.0") ) def valid_ascii_username_and_utf8_password(self, server): - """Check that we can login using valid ascii username with utf-8 password.""" + """Check that we can login using valid ascii username with utf-8 password. + """ username = "utf8_password" user = {"cn": username, "userpassword": "utf8_password_Gãńdåłf_Thê_Gręât"} - add_user_to_ldap_and_login(user=user, server=server) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + add_user_to_ldap_and_login(user=user, server=server) @TestScenario def empty_username_and_empty_password(self, server=None): """Check that we can login using empty username and empty password as - it will use the default user and that has an empty password.""" - login_and_execute_query(username="", password="") + it will use the default user and that has an empty password. + """ + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + login_and_execute_query(username="", password="") @TestScenario @Requirements( @@ -698,16 +763,18 @@ def user_lookup_priority(self, server): "ldap": {"username": "ldap", "password": "userldap"} } - with ldap_users(*[{"cn": user["username"], "userpassword": user["password"]} for user in users.values()]): - with rbac_users({"cn": "local", "userpassword": "local"}): - with When("I try to login as 'default' user which is also defined in users.xml it should fail"): - login_and_execute_query(**users["default"], exitcode=exitcode, message=message.format(username="default")) + with rbac_roles("ldap_role") as roles: + with ldap_external_user_directory(server=server, roles=roles, restart=True): + with ldap_users(*[{"cn": user["username"], "userpassword": user["password"]} for user in users.values()]): + with rbac_users({"cn": "local", "userpassword": "local"}): + with When("I try to login as 'default' user which is also defined in users.xml it should fail"): + login_and_execute_query(**users["default"], exitcode=exitcode, message=message.format(username="default")) - with When("I try to login as 'local' user which is also defined in local storage it should fail"): - login_and_execute_query(**users["local"], exitcode=exitcode, message=message.format(username="local")) + with When("I try to login as 'local' user which is also defined in local storage it should fail"): + login_and_execute_query(**users["local"], exitcode=exitcode, message=message.format(username="local")) - with When("I try to login as 'ldap' user defined only in LDAP it should work"): - login_and_execute_query(**users["ldap"]) + with When("I try to login as 'ldap' user defined only in LDAP it should work"): + login_and_execute_query(**users["ldap"]) @TestOutline(Feature) @@ -728,7 +795,5 @@ def feature(self, servers=None, server=None, node="clickhouse1"): server = "openldap1" with ldap_servers(servers): - with rbac_roles("ldap_role") as roles: - with ldap_external_user_directory(server=server, roles=roles, restart=True): - for scenario in loads(current_module(), Scenario): - Scenario(test=scenario, flags=TE)(server=server) + for scenario in loads(current_module(), Scenario): + Scenario(test=scenario, flags=TE)(server=server) diff --git a/tests/testflows/ldap/external_user_directory/tests/common.py b/tests/testflows/ldap/external_user_directory/tests/common.py index b4a8c9e6640..d6f414e617a 100644 --- a/tests/testflows/ldap/external_user_directory/tests/common.py +++ b/tests/testflows/ldap/external_user_directory/tests/common.py @@ -70,6 +70,15 @@ def rbac_roles(*roles): with By(f"dropping role {role}", flags=TE): node.query(f"DROP ROLE IF EXISTS {role}") +def verify_ldap_user_exists(server, username, password): + """Check that LDAP user is defined on the LDAP server. + """ + with By("searching LDAP database"): + ldap_node = current().context.cluster.node(server) + r = ldap_node.command( + f"ldapwhoami -H ldap://localhost -D 'cn={username},ou=users,dc=company,dc=com' -w {password}") + assert r.exitcode == 0, error() + def create_ldap_external_user_directory_config_content(server=None, roles=None, **kwargs): """Create LDAP external user directory configuration file content. """ @@ -197,8 +206,26 @@ def login(servers, directory_server, *users, config=None): @TestStep(When) @Name("I login as {username} and execute query") -def login_and_execute_query(self, username, password, exitcode=None, message=None, steps=True, timeout=60): - self.context.node.query("SELECT 1", - settings=[("user", username), ("password", password)], - exitcode=exitcode or 0, - message=message, steps=steps, timeout=timeout) +def login_and_execute_query(self, username, password, exitcode=None, message=None, steps=True, timeout=60, poll=False): + if poll: + start_time = time.time() + attempt = 0 + + with By("repeatedly trying to login until successful or timeout"): + while True: + with When(f"attempt #{attempt}"): + r = self.context.node.query("SELECT 1", settings=[("user", username), ("password", password)], + no_checks=True, steps=False, timeout=timeout) + + if r.exitcode == (0 if exitcode is None else exitcode) and (message in r.output if message is not None else True): + break + + if time.time() - start_time > timeout: + fail(f"timeout {timeout} trying to login") + + attempt += 1 + else: + self.context.node.query("SELECT 1", + settings=[("user", username), ("password", password)], + exitcode=(0 if exitcode is None else exitcode), + message=message, steps=steps, timeout=timeout)