|
3 | 3 | # See LICENSE file for licensing details.
|
4 | 4 | import asyncio
|
5 | 5 | import logging
|
| 6 | +import secrets |
| 7 | +import string |
6 | 8 | from pathlib import Path
|
7 | 9 |
|
8 | 10 | import psycopg2
|
|
27 | 29 | APPLICATION_APP_NAME = "application"
|
28 | 30 | DATABASE_APP_NAME = "database"
|
29 | 31 | ANOTHER_DATABASE_APP_NAME = "another-database"
|
| 32 | +DATA_INTEGRATOR_APP_NAME = "data-integrator" |
30 | 33 | APP_NAMES = [APPLICATION_APP_NAME, DATABASE_APP_NAME, ANOTHER_DATABASE_APP_NAME]
|
31 | 34 | DATABASE_APP_METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
|
32 | 35 | FIRST_DATABASE_RELATION_NAME = "first-database"
|
@@ -397,3 +400,87 @@ async def test_relation_with_no_database_name(ops_test: OpsTest):
|
397 | 400 | f"{DATABASE_APP_NAME}", f"{APPLICATION_APP_NAME}:{NO_DATABASE_RELATION_NAME}"
|
398 | 401 | )
|
399 | 402 | await ops_test.model.wait_for_idle(apps=APP_NAMES, status="active", raise_on_blocked=True)
|
| 403 | + |
| 404 | + |
| 405 | +async def test_admin_role(ops_test: OpsTest): |
| 406 | + """Test that the admin role gives access to all the databases.""" |
| 407 | + all_app_names = [DATA_INTEGRATOR_APP_NAME] |
| 408 | + all_app_names.extend(APP_NAMES) |
| 409 | + async with ops_test.fast_forward(): |
| 410 | + await ops_test.model.deploy(DATA_INTEGRATOR_APP_NAME) |
| 411 | + await ops_test.model.wait_for_idle(apps=[DATA_INTEGRATOR_APP_NAME], status="blocked") |
| 412 | + await ops_test.model.applications[DATA_INTEGRATOR_APP_NAME].set_config( |
| 413 | + { |
| 414 | + "database-name": DATA_INTEGRATOR_APP_NAME.replace("-", "_"), |
| 415 | + "extra-user-roles": "admin", |
| 416 | + } |
| 417 | + ) |
| 418 | + await ops_test.model.wait_for_idle(apps=[DATA_INTEGRATOR_APP_NAME], status="blocked") |
| 419 | + await ops_test.model.add_relation(DATA_INTEGRATOR_APP_NAME, DATABASE_APP_NAME) |
| 420 | + await ops_test.model.wait_for_idle(apps=all_app_names, status="active") |
| 421 | + |
| 422 | + # Check that the user can access all the databases. |
| 423 | + for database in [ |
| 424 | + "postgres", |
| 425 | + "application_first_database", |
| 426 | + "another_application_first_database", |
| 427 | + ]: |
| 428 | + logger.info(f"connecting to the following database: {database}") |
| 429 | + connection_string = await build_connection_string( |
| 430 | + ops_test, DATA_INTEGRATOR_APP_NAME, "postgresql", database=database |
| 431 | + ) |
| 432 | + connection = None |
| 433 | + should_fail = False |
| 434 | + try: |
| 435 | + with psycopg2.connect(connection_string) as connection, connection.cursor() as cursor: |
| 436 | + # Check the version that the application received is the same on the |
| 437 | + # database server. |
| 438 | + cursor.execute("SELECT version();") |
| 439 | + data = cursor.fetchone()[0].split(" ")[1] |
| 440 | + |
| 441 | + # Get the version of the database and compare with the information that |
| 442 | + # was retrieved directly from the database. |
| 443 | + version = await get_application_relation_data( |
| 444 | + ops_test, DATA_INTEGRATOR_APP_NAME, "postgresql", "version" |
| 445 | + ) |
| 446 | + assert version == data |
| 447 | + |
| 448 | + # Write some data (it should fail in the "postgres" database). |
| 449 | + random_name = ( |
| 450 | + f"test_{''.join(secrets.choice(string.ascii_lowercase) for _ in range(10))}" |
| 451 | + ) |
| 452 | + should_fail = database == "postgres" |
| 453 | + cursor.execute(f"CREATE TABLE {random_name}(data TEXT);") |
| 454 | + if should_fail: |
| 455 | + assert ( |
| 456 | + False |
| 457 | + ), f"failed to run a statement in the following database: {database}" |
| 458 | + except psycopg2.errors.InsufficientPrivilege as e: |
| 459 | + if not should_fail: |
| 460 | + logger.exception(e) |
| 461 | + assert ( |
| 462 | + False |
| 463 | + ), f"failed to connect to or run a statement in the following database: {database}" |
| 464 | + finally: |
| 465 | + if connection is not None: |
| 466 | + connection.close() |
| 467 | + |
| 468 | + # Test the creation and deletion of databases. |
| 469 | + connection_string = await build_connection_string( |
| 470 | + ops_test, DATA_INTEGRATOR_APP_NAME, "postgresql", database="postgres" |
| 471 | + ) |
| 472 | + connection = psycopg2.connect(connection_string) |
| 473 | + connection.autocommit = True |
| 474 | + cursor = connection.cursor() |
| 475 | + random_name = f"test_{''.join(secrets.choice(string.ascii_lowercase) for _ in range(10))}" |
| 476 | + cursor.execute(f"CREATE DATABASE {random_name};") |
| 477 | + cursor.execute(f"DROP DATABASE {random_name};") |
| 478 | + try: |
| 479 | + cursor.execute("DROP DATABASE postgres;") |
| 480 | + assert False, "the admin extra user role was able to drop the `postgres` system database" |
| 481 | + except psycopg2.errors.InsufficientPrivilege: |
| 482 | + # Ignore the error, as the admin extra user role mustn't be able to drop |
| 483 | + # the "postgres" system database. |
| 484 | + pass |
| 485 | + finally: |
| 486 | + connection.close() |
0 commit comments