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:
@@ -371,6 +371,8 @@ from .auth import (
|
||||
validate_password_strength,
|
||||
PasswordTooShortError,
|
||||
MIN_PASSWORD_LENGTH,
|
||||
check_project_access,
|
||||
AuthorizationService,
|
||||
)
|
||||
|
||||
|
||||
@@ -1064,10 +1066,13 @@ def create_project(
|
||||
|
||||
|
||||
@router.get("/api/v1/projects/{project_name}", response_model=ProjectResponse)
|
||||
def get_project(project_name: str, db: Session = Depends(get_db)):
|
||||
project = db.query(Project).filter(Project.name == project_name).first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
def get_project(
|
||||
project_name: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: Optional[User] = Depends(get_current_user_optional),
|
||||
):
|
||||
"""Get a single project by name. Requires read access for private projects."""
|
||||
project = check_project_access(db, project_name, current_user, "read")
|
||||
return project
|
||||
|
||||
|
||||
@@ -1077,13 +1082,11 @@ def update_project(
|
||||
project_update: ProjectUpdate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: Optional[User] = Depends(get_current_user_optional),
|
||||
):
|
||||
"""Update a project's metadata."""
|
||||
user_id = get_user_id(request)
|
||||
|
||||
project = db.query(Project).filter(Project.name == project_name).first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
"""Update a project's metadata. Requires admin access."""
|
||||
project = check_project_access(db, project_name, current_user, "admin")
|
||||
user_id = current_user.username if current_user else get_user_id(request)
|
||||
|
||||
# Track changes for audit log
|
||||
changes = {}
|
||||
@@ -1130,14 +1133,16 @@ def delete_project(
|
||||
project_name: str,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: Optional[User] = Depends(get_current_user_optional),
|
||||
):
|
||||
"""
|
||||
Delete a project and all its packages.
|
||||
Delete a project and all its packages. Requires admin access.
|
||||
|
||||
Decrements ref_count for all artifacts referenced by tags in all packages
|
||||
within this project.
|
||||
"""
|
||||
user_id = get_user_id(request)
|
||||
check_project_access(db, project_name, current_user, "admin")
|
||||
user_id = current_user.username if current_user else get_user_id(request)
|
||||
|
||||
project = db.query(Project).filter(Project.name == project_name).first()
|
||||
if not project:
|
||||
@@ -1453,10 +1458,10 @@ def create_package(
|
||||
package: PackageCreate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: Optional[User] = Depends(get_current_user_optional),
|
||||
):
|
||||
project = db.query(Project).filter(Project.name == project_name).first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
"""Create a new package in a project. Requires write access."""
|
||||
project = check_project_access(db, project_name, current_user, "write")
|
||||
|
||||
# Validate format
|
||||
if package.format not in PACKAGE_FORMATS:
|
||||
@@ -1680,14 +1685,12 @@ def upload_artifact(
|
||||
- Authorization: Bearer <api-key> for authentication
|
||||
"""
|
||||
start_time = time.time()
|
||||
user_id = get_user_id_from_request(request, db, current_user)
|
||||
settings = get_settings()
|
||||
storage_result = None
|
||||
|
||||
# Get project and package
|
||||
project = db.query(Project).filter(Project.name == project_name).first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
# Check authorization (write access required for uploads)
|
||||
project = check_project_access(db, project_name, current_user, "write")
|
||||
user_id = current_user.username if current_user else get_user_id_from_request(request, db, current_user)
|
||||
|
||||
package = (
|
||||
db.query(Package)
|
||||
@@ -2312,6 +2315,7 @@ def download_artifact(
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
storage: S3Storage = Depends(get_storage),
|
||||
current_user: Optional[User] = Depends(get_current_user_optional),
|
||||
range: Optional[str] = Header(None),
|
||||
mode: Optional[Literal["proxy", "redirect", "presigned"]] = Query(
|
||||
default=None,
|
||||
@@ -2347,10 +2351,8 @@ def download_artifact(
|
||||
"""
|
||||
settings = get_settings()
|
||||
|
||||
# Get project and package
|
||||
project = db.query(Project).filter(Project.name == project_name).first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
# Check authorization (read access required for downloads)
|
||||
project = check_project_access(db, project_name, current_user, "read")
|
||||
|
||||
package = (
|
||||
db.query(Package)
|
||||
@@ -2568,10 +2570,8 @@ def get_artifact_url(
|
||||
"""
|
||||
settings = get_settings()
|
||||
|
||||
# Get project and package
|
||||
project = db.query(Project).filter(Project.name == project_name).first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
# Check authorization (read access required for downloads)
|
||||
project = check_project_access(db, project_name, current_user, "read")
|
||||
|
||||
package = (
|
||||
db.query(Package)
|
||||
@@ -2826,12 +2826,11 @@ def create_tag(
|
||||
tag: TagCreate,
|
||||
request: Request,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: Optional[User] = Depends(get_current_user_optional),
|
||||
):
|
||||
user_id = get_user_id(request)
|
||||
|
||||
project = db.query(Project).filter(Project.name == project_name).first()
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="Project not found")
|
||||
"""Create or update a tag. Requires write access."""
|
||||
project = check_project_access(db, project_name, current_user, "write")
|
||||
user_id = current_user.username if current_user else get_user_id(request)
|
||||
|
||||
package = (
|
||||
db.query(Package)
|
||||
|
||||
Reference in New Issue
Block a user