test_rbac_api.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. from __future__ import annotations
  2. import os
  3. import uuid
  4. import pytest
  5. from sqlalchemy import create_engine, text
  6. pytestmark = pytest.mark.integration
  7. @pytest.fixture()
  8. def rbac_client(monkeypatch):
  9. database_url = os.environ.get("TEST_DATABASE_URL")
  10. admin_password = os.environ.get("TEST_ADMIN_PASSWORD")
  11. if not database_url or not admin_password:
  12. pytest.skip("TEST_DATABASE_URL and TEST_ADMIN_PASSWORD are required")
  13. monkeypatch.setenv("DATABASE_URL", database_url)
  14. monkeypatch.setenv("SECRET_KEY", "dataops-local-test-secret-key")
  15. from app import create_app
  16. app = create_app()
  17. app.config.update(TESTING=True)
  18. return app.test_client(), database_url, admin_password
  19. def _login(client, username, password):
  20. response = client.post(
  21. "/api/system/auth/login", json={"username": username, "password": password}
  22. )
  23. return response, (response.get_json().get("data") or {}).get("token")
  24. def test_admin_manages_users_and_viewer_is_forbidden(rbac_client):
  25. client, database_url, admin_password = rbac_client
  26. username = f"viewer_{uuid.uuid4().hex[:10]}"
  27. user_id = None
  28. try:
  29. login, admin_token = _login(client, "admin", admin_password)
  30. assert login.status_code == 200
  31. assert admin_token
  32. me = client.get(
  33. "/api/system/auth/me",
  34. headers={"Authorization": f"Bearer {admin_token}"},
  35. )
  36. assert me.status_code == 200
  37. assert "users:manage" in me.get_json()["data"]["permissions"]
  38. created = client.post(
  39. "/api/system/users",
  40. json={
  41. "username": username,
  42. "password": "ViewerPass123",
  43. "roles": ["viewer"],
  44. },
  45. headers={"Authorization": f"Bearer {admin_token}"},
  46. )
  47. assert created.status_code == 201
  48. user_id = created.get_json()["data"]["id"]
  49. viewer_login, viewer_token = _login(client, username, "ViewerPass123")
  50. assert viewer_login.status_code == 200
  51. forbidden = client.get(
  52. "/api/system/users",
  53. headers={"Authorization": f"Bearer {viewer_token}"},
  54. )
  55. assert forbidden.status_code == 403
  56. disabled = client.put(
  57. f"/api/system/users/{user_id}",
  58. json={"status": "disabled"},
  59. headers={"Authorization": f"Bearer {admin_token}"},
  60. )
  61. assert disabled.status_code == 200
  62. rejected, _ = _login(client, username, "ViewerPass123")
  63. assert rejected.status_code == 401
  64. invalid = client.get(
  65. "/api/system/auth/me",
  66. headers={"Authorization": "Bearer invalid-token"},
  67. )
  68. assert invalid.status_code == 401
  69. finally:
  70. if user_id:
  71. engine = create_engine(database_url)
  72. with engine.begin() as connection:
  73. connection.execute(
  74. text("DELETE FROM public.users WHERE id = CAST(:id AS uuid)"),
  75. {"id": user_id},
  76. )
  77. engine.dispose()