Files
gatehouse-api/tests/integration/test_authorization.py
T
nexgen_mirrors 015c622016 test: add comprehensive integration test suite for IAM platform
Add 162 integration tests covering authentication flows, TOTP MFA,
SSH key/certificate management, organization workflows, multi-org
access, self-service features, admin operations, authorization,
security edge cases, department/principal management, CA management,
policy compliance, WebAuthn passkeys, and ZeroTier network access.

Includes:
- Reusable API client library with session management
- Test fixtures for users, organizations, memberships, and CAs
- Helper functions for SSH key generation and verification
- Documentation for running and writing tests

Also update test configuration to disable conflicting maas plugins
and configure WebAuthn/session settings for localhost testing.
2026-04-23 15:41:37 +09:30

169 lines
7.3 KiB
Python

"""Authorization and access control integration tests.
Covers RBAC enforcement, cross-user isolation, and soft-delete behavior.
"""
import pytest
from tests.integration.client.base import ApiError
from gatehouse_app.utils.constants import OrganizationRole
def assert_error(exc: ApiError, expected_status: int, expected_error_type: str | None = None):
assert exc.status_code == expected_status
if expected_error_type:
assert exc.error_type == expected_error_type
class TestAuthorization:
"""Test access control across endpoints."""
def test_access_protected_without_auth_negative(self, integration_client):
"""TEST: AUTHZ-01 — Access protected endpoint without auth.
WHAT: Call GET /auth/me with no token.
WHY: All protected endpoints must require authentication.
EXPECTED: 401 Unauthorized.
"""
integration_client.clear_token()
with pytest.raises(ApiError) as exc_info:
integration_client.auth.me()
assert exc_info.value.status_code == 401
def test_member_attempts_admin_operation_negative(self, integration_client, create_test_user, create_test_org, create_test_membership):
"""TEST: AUTHZ-02 — Member attempts admin operation.
WHAT: Member tries to delete an organization.
WHY: Role-based access must be enforced.
EXPECTED: 403 Forbidden.
"""
member = create_test_user(password="MemberPass123!")
org = create_test_org()
create_test_membership(member["id"], org["id"], OrganizationRole.MEMBER)
integration_client.auth.login(email=member["email"], password="MemberPass123!")
with pytest.raises(ApiError) as exc_info:
integration_client.orgs.delete(org["id"], confirm=True)
assert exc_info.value.status_code == 403
def test_admin_attempts_owner_operation_negative(self, integration_client, create_test_user, create_test_org, create_test_membership):
"""TEST: AUTHZ-03 — Admin attempts owner-only operation.
WHAT: Admin tries to transfer ownership.
WHY: Ownership transfer is owner-only.
EXPECTED: 403 Forbidden.
"""
owner = create_test_user(password="OwnerPass123!")
admin = create_test_user(password="AdminPass123!")
org = create_test_org()
create_test_membership(owner["id"], org["id"], OrganizationRole.OWNER)
create_test_membership(admin["id"], org["id"], OrganizationRole.ADMIN)
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
with pytest.raises(ApiError) as exc_info:
integration_client.orgs.transfer_ownership(org["id"], owner["id"])
assert exc_info.value.status_code == 403
def test_non_member_attempts_org_operation_negative(self, integration_client, create_test_user, create_test_org):
"""TEST: AUTHZ-04 — Non-member attempts org operation.
WHAT: Unrelated user tries to GET an organization.
WHY: Org data must not leak to outsiders.
EXPECTED: 403 Forbidden.
"""
org = create_test_org()
user = create_test_user(password="MyPassword123!")
integration_client.auth.login(email=user["email"], password="MyPassword123!")
with pytest.raises(ApiError) as exc_info:
integration_client.orgs.get(org["id"])
assert exc_info.value.status_code == 403
def test_user_a_accesses_user_b_ssh_keys_negative(self, integration_app, integration_client, create_test_user):
"""TEST: AUTHZ-05 — User A accesses User B's SSH keys.
WHAT: User A tries to GET User B's SSH key.
WHY: Cross-user data isolation.
EXPECTED: 403 Forbidden.
"""
from tests.integration.fixtures.ssh_keys import TEST_PUBLIC_KEY
user_a = create_test_user(password="PassA123!")
user_b = create_test_user(password="PassB123!")
integration_client.auth.login(email=user_b["email"], password="PassB123!")
add_result = integration_client.ssh.add_key(TEST_PUBLIC_KEY, "User B Key")
key_id = add_result["data"]["id"]
integration_client.auth.logout()
integration_client.auth.login(email=user_a["email"], password="PassA123!")
with pytest.raises(ApiError) as exc_info:
integration_client.ssh.get_key(key_id)
assert exc_info.value.status_code == 403
def test_user_a_accesses_user_b_sessions_negative(self, integration_app, integration_client, create_test_user):
"""TEST: AUTHZ-07 — User A accesses User B's sessions.
WHAT: User A tries to list User B's sessions.
WHY: Session data is private.
EXPECTED: 403 Forbidden or only own sessions returned.
"""
user_a = create_test_user(password="PassA123!")
user_b = create_test_user(password="PassB123!")
integration_client.auth.login(email=user_b["email"], password="PassB123!")
sessions_b = integration_client.auth.list_sessions()
session_id_b = sessions_b["data"]["sessions"][0]["id"]
integration_client.auth.logout()
integration_client.auth.login(email=user_a["email"], password="PassA123!")
# User A should not be able to revoke User B's session
with pytest.raises(ApiError) as exc_info:
integration_client.auth.revoke_session(session_id_b)
assert exc_info.value.status_code == 404
def test_soft_deleted_user_cannot_login_negative(self, integration_app, integration_client, create_test_user):
"""TEST: AUTHZ-08 — Soft-deleted user cannot login.
WHAT: Create a user, soft-delete them, attempt login.
WHY: Soft delete must block access.
EXPECTED: 401 or 404.
"""
from gatehouse_app.extensions import db
from gatehouse_app.models.user.user import User
user = create_test_user(password="MyPassword123!")
with integration_app.app_context():
db_user = User.query.get(user["id"])
db_user.deleted_at = db.func.now()
db.session.commit()
with pytest.raises(ApiError) as exc_info:
integration_client.auth.login(email=user["email"], password="MyPassword123!")
assert exc_info.value.status_code in (400, 401, 404)
def test_soft_deleted_org_not_listable_negative(self, integration_app, integration_client, create_test_user, create_test_org, create_test_membership):
"""TEST: AUTHZ-09 — Soft-deleted org not listable.
WHAT: Create an org, soft-delete it, then GET /users/me/organizations.
WHY: Soft-deleted orgs should not appear.
EXPECTED: Org not in the list.
"""
from gatehouse_app.extensions import db
from gatehouse_app.models.organization.organization import Organization
user = create_test_user(password="MyPassword123!")
org = create_test_org()
create_test_membership(user["id"], org["id"], OrganizationRole.MEMBER)
with integration_app.app_context():
db_org = Organization.query.get(org["id"])
db_org.deleted_at = db.func.now()
db.session.commit()
integration_client.auth.login(email=user["email"], password="MyPassword123!")
result = integration_client.users.get_my_organizations()
orgs = result.get("data", {}).get("organizations", [])
assert not any(o.get("id") == org["id"] for o in orgs)