Add project-level authorization checks

Authorization:
- Add AuthorizationService for checking project access
- Implement get_user_access_level() with admin, owner, and permission checks
- Add check_project_access() helper for route handlers
- Add grant_access() and revoke_access() methods
- Add ProjectAccessChecker dependency class

Routes:
- Add authorization checks to project CRUD (read, update, delete)
- Add authorization checks to package create
- Add authorization checks to upload endpoint (requires write)
- Add authorization checks to download endpoint (requires read)
- Add authorization checks to tag create

Tests:
- Fix pagination flakiness in test_list_projects
- Fix pagination flakiness in test_projects_search
- Add API key authentication to concurrent upload test
This commit is contained in:
Mondo Diaz
2026-01-08 16:20:42 -06:00
parent b1c17e8ab7
commit d61c7a71fb
5 changed files with 316 additions and 37 deletions

View File

@@ -59,7 +59,8 @@ class TestProjectCRUD:
@pytest.mark.integration
def test_list_projects(self, integration_client, test_project):
"""Test listing projects includes created project."""
response = integration_client.get("/api/v1/projects")
# Search specifically for our test project to avoid pagination issues
response = integration_client.get(f"/api/v1/projects?search={test_project}")
assert response.status_code == 200
data = response.json()
@@ -107,9 +108,11 @@ class TestProjectListingFilters:
@pytest.mark.integration
def test_projects_search(self, integration_client, test_project):
"""Test project search by name."""
# Search for our test project
# Search using the unique portion of our test project name
# test_project format is "test-project-test-{uuid[:8]}"
unique_part = test_project.split("-")[-1] # Get the UUID portion
response = integration_client.get(
f"/api/v1/projects?search={test_project[:10]}"
f"/api/v1/projects?search={unique_part}"
)
assert response.status_code == 200

View File

@@ -286,6 +286,14 @@ class TestConcurrentUploads:
expected_hash = compute_sha256(content)
num_concurrent = 5
# Create an API key for worker threads
api_key_response = integration_client.post(
"/api/v1/auth/keys",
json={"name": "concurrent-test-key"},
)
assert api_key_response.status_code == 200, f"Failed to create API key: {api_key_response.text}"
api_key = api_key_response.json()["key"]
results = []
errors = []
@@ -306,6 +314,7 @@ class TestConcurrentUploads:
f"/api/v1/project/{project}/{package}/upload",
files=files,
data={"tag": f"concurrent-{tag_suffix}"},
headers={"Authorization": f"Bearer {api_key}"},
)
if response.status_code == 200:
results.append(response.json())