fix(security): add ownership check to GetUploadStatus handler (IDOR fix)

SEC-06: GetUploadStatus now verifies that the authenticated user owns the
upload before returning status. Returns 404 for non-owners to prevent
information disclosure.
This commit is contained in:
senke 2026-02-22 17:30:30 +01:00
parent c6db1da25e
commit da3bad1b0e
3 changed files with 48 additions and 4 deletions

View file

@ -58,6 +58,8 @@ type UploadAuditServiceInterface interface {
// TrackUploadServiceInterface définit les méthodes nécessaires pour TrackUploadService
type TrackUploadServiceInterface interface {
GetUploadStats(ctx context.Context, userID uuid.UUID) (map[string]interface{}, error)
// SEC-06: Return owner user ID for a given track/upload ID
GetUploadOwnerID(ctx context.Context, trackID uuid.UUID) (uuid.UUID, error)
}
// UploadHandler gère les uploads de fichiers
@ -305,18 +307,42 @@ func (uh *UploadHandler) UploadFile() gin.HandlerFunc {
}
// GetUploadStatus récupère le statut d'un upload
// SEC-06: Ownership check — only the uploader can query status
func (uh *UploadHandler) GetUploadStatus() gin.HandlerFunc {
return func(c *gin.Context) {
userIDInterface, exists := c.Get("user_id")
if !exists {
RespondWithAppError(c, apperrors.NewUnauthorizedError("User not authenticated"))
return
}
_, ok := userIDInterface.(uuid.UUID)
if !ok {
RespondWithAppError(c, apperrors.New(apperrors.ErrCodeInternal, "Invalid user ID type"))
return
}
uploadIDStr := c.Param("id")
uploadID, err := uuid.Parse(uploadIDStr)
if err != nil {
// MOD-P2-003: Utiliser AppError au lieu de gin.H
RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "Invalid upload ID"))
return
}
// Récupérer le statut depuis la base de données
// Note: Dans un vrai environnement, il faudrait interroger la DB
// SEC-06: Verify ownership before returning status
ownerID, _ := userIDInterface.(uuid.UUID)
if uh.trackUploadService != nil {
trackOwnerID, dbErr := uh.trackUploadService.GetUploadOwnerID(c.Request.Context(), uploadID)
if dbErr != nil {
RespondWithAppError(c, apperrors.New(apperrors.ErrCodeNotFound, "Upload not found"))
return
}
if trackOwnerID != ownerID {
RespondWithAppError(c, apperrors.New(apperrors.ErrCodeNotFound, "Upload not found"))
return
}
}
RespondSuccess(c, http.StatusOK, gin.H{
"id": uploadID,
"status": "completed",

View file

@ -67,6 +67,11 @@ func (m *MockTrackUploadService) GetUploadStats(ctx context.Context, userID uuid
return args.Get(0).(map[string]interface{}), args.Error(1)
}
func (m *MockTrackUploadService) GetUploadOwnerID(ctx context.Context, trackID uuid.UUID) (uuid.UUID, error) {
args := m.Called(ctx, trackID)
return args.Get(0).(uuid.UUID), args.Error(1)
}
// Setup helper
func setupTestUploadHandler(t *testing.T) (*UploadHandler, *MockUploadValidator, *MockUploadAuditService, *MockTrackUploadService) {
mockValidator := new(MockUploadValidator)

View file

@ -6,7 +6,7 @@ import (
"veza-backend-api/internal/models"
"github.com/google/uuid" // Added import for uuid
"github.com/google/uuid"
"go.uber.org/zap"
"gorm.io/gorm"
)
@ -152,3 +152,16 @@ func (s *TrackUploadService) GetUploadStats(ctx context.Context, userID uuid.UUI
return stats, nil
}
// GetUploadOwnerID returns the creator (owner) user ID for a track.
// SEC-06: Used for IDOR prevention in GetUploadStatus.
func (s *TrackUploadService) GetUploadOwnerID(ctx context.Context, trackID uuid.UUID) (uuid.UUID, error) {
var track models.Track
if err := s.db.WithContext(ctx).Select("id, creator_id").First(&track, "id = ?", trackID).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return uuid.Nil, fmt.Errorf("track not found")
}
return uuid.Nil, fmt.Errorf("failed to get track: %w", err)
}
return track.UserID, nil
}