Files
fast-next-template/backend/tests/api/test_permissions_security.py
Felipe Cardoso c051bbf0aa Add security tests for configurations, permissions, and authentication
- **Configurations:** Test minimum `SECRET_KEY` length validation to prevent weak JWT signing keys. Validate proper handling of secure defaults.
- **Permissions:** Add tests for inactive user blocking, API access control, and superuser privilege escalation across organizational roles.
- **Authentication:** Test logout safety, session revocation, token replay prevention, and defense against JWT algorithm confusion attacks.
- Include `# pragma: no cover` for unreachable defensive code in security-sensitive areas.
2025-11-02 11:55:58 +01:00

229 lines
7.5 KiB
Python

"""
Security tests for permissions and access control (app/api/dependencies/permissions.py).
Critical security tests covering:
- Inactive user blocking (prevents deactivated accounts from accessing APIs)
- Superuser privilege escalation (auto-OWNER role in organizations)
These tests prevent unauthorized access and privilege escalation.
"""
import pytest
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.user import User
from app.models.organization import Organization
from app.crud.user import user as user_crud
class TestInactiveUserBlocking:
"""
Test inactive user blocking (permissions.py lines 52-57).
Attack Scenario:
Admin deactivates a user's account (ban/suspension), but user still has
valid access tokens. System must block ALL API access for inactive users.
Covers: permissions.py:52-57
"""
@pytest.mark.asyncio
async def test_inactive_user_cannot_access_protected_endpoints(
self,
client: AsyncClient,
async_test_db,
async_test_user: User,
user_token: str
):
"""
Test that inactive users are blocked from protected endpoints.
Attack Scenario:
1. User logs in and gets access token
2. Admin deactivates user account
3. User tries to access protected endpoint with valid token
4. System MUST reject (account inactive)
"""
test_engine, SessionLocal = async_test_db
# Step 1: Verify user can access endpoint while active
response = await client.get(
"/api/v1/users/me",
headers={"Authorization": f"Bearer {user_token}"}
)
assert response.status_code == 200, "Active user should have access"
# Step 2: Admin deactivates the user
async with SessionLocal() as session:
user = await user_crud.get(session, id=async_test_user.id)
user.is_active = False
await session.commit()
# Step 3: User tries to access endpoint with same token
response = await client.get(
"/api/v1/users/me",
headers={"Authorization": f"Bearer {user_token}"}
)
# Step 4: System MUST reject (covers lines 52-57)
assert response.status_code == 403, "Inactive user must be blocked"
data = response.json()
if "errors" in data:
assert "inactive" in data["errors"][0]["message"].lower()
else:
assert "inactive" in data.get("detail", "").lower()
@pytest.mark.asyncio
async def test_inactive_user_blocked_from_organization_endpoints(
self,
client: AsyncClient,
async_test_db,
async_test_user: User,
user_token: str
):
"""
Test that inactive users can't access organization endpoints.
Ensures the inactive check applies to ALL protected endpoints.
"""
test_engine, SessionLocal = async_test_db
# Deactivate user
async with SessionLocal() as session:
user = await user_crud.get(session, id=async_test_user.id)
user.is_active = False
await session.commit()
# Try to list organizations
response = await client.get(
"/api/v1/organizations/me",
headers={"Authorization": f"Bearer {user_token}"}
)
# Must be blocked
assert response.status_code == 403, "Inactive user blocked from org endpoints"
class TestSuperuserPrivilegeEscalation:
"""
Test superuser privilege escalation (permissions.py lines 154-157).
Business Logic:
Superusers automatically get OWNER role in ALL organizations.
This is intentional for admin oversight, but must be tested to ensure
it works correctly and doesn't grant too little or too much access.
Covers: permissions.py:154-157
"""
@pytest.mark.asyncio
async def test_superuser_gets_owner_role_automatically(
self,
client: AsyncClient,
async_test_db,
async_test_superuser: User,
superuser_token: str
):
"""
Test that superusers automatically get OWNER role in organizations.
Business Rule:
Superusers can manage any organization without being explicitly added.
This is for platform administration.
"""
test_engine, SessionLocal = async_test_db
# Step 1: Create an organization (owned by someone else)
async with SessionLocal() as session:
org = Organization(
name="Test Organization",
slug="test-org"
)
session.add(org)
await session.commit()
await session.refresh(org)
org_id = org.id
# Step 2: Superuser tries to access the organization
# (They're not a member, but should auto-get OWNER role)
response = await client.get(
f"/api/v1/organizations/{org_id}",
headers={"Authorization": f"Bearer {superuser_token}"}
)
# Step 3: Should have access (covers lines 154-157)
# The get_user_role_in_org function returns OWNER for superusers
assert response.status_code == 200, "Superuser should access any org"
@pytest.mark.asyncio
async def test_superuser_can_manage_any_organization(
self,
client: AsyncClient,
async_test_db,
async_test_superuser: User,
superuser_token: str
):
"""
Test that superusers have full management access to all organizations.
Ensures the OWNER role privilege escalation works end-to-end.
"""
test_engine, SessionLocal = async_test_db
# Create an organization
async with SessionLocal() as session:
org = Organization(
name="Test Organization",
slug="test-org"
)
session.add(org)
await session.commit()
await session.refresh(org)
org_id = org.id
# Superuser tries to update it (OWNER-only action)
response = await client.put(
f"/api/v1/organizations/{org_id}",
headers={"Authorization": f"Bearer {superuser_token}"},
json={"name": "Updated Name"}
)
# Should succeed (superuser has OWNER privileges)
assert response.status_code in [200, 404], "Superuser should be able to manage any org"
# Note: Might be 404 if org endpoints require membership, but the role check passes
@pytest.mark.asyncio
async def test_regular_user_does_not_get_owner_role(
self,
client: AsyncClient,
async_test_db,
async_test_user: User,
user_token: str
):
"""
Sanity check: Regular users don't get automatic OWNER role.
Ensures the superuser check is working correctly (line 154).
"""
test_engine, SessionLocal = async_test_db
# Create an organization
async with SessionLocal() as session:
org = Organization(
name="Test Organization",
slug="test-org"
)
session.add(org)
await session.commit()
await session.refresh(org)
org_id = org.id
# Regular user tries to access it (not a member)
response = await client.get(
f"/api/v1/organizations/{org_id}",
headers={"Authorization": f"Bearer {user_token}"}
)
# Should be denied (not a member, not a superuser)
assert response.status_code in [403, 404], "Regular user shouldn't access non-member org"