Skip to content

Commit cf5b0b6

Browse files
Stefan ErnstStefan Ernst
authored andcommitted
Fixed permission checks across the app
1 parent 4e267c4 commit cf5b0b6

13 files changed

Lines changed: 616 additions & 91 deletions

File tree

internal/handlers/items_permissions.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,7 @@ func (h *ItemHandler) canAccessInactiveWorkspace(user *models.User, workspaceID
101101
return false, nil
102102
}
103103

104-
// System admins can always access inactive workspaces
105-
isSystemAdmin, err := h.permissionService.IsSystemAdmin(user.ID)
106-
if err == nil && isSystemAdmin {
107-
return true, nil
108-
}
109-
110-
// Check if user has workspace admin permission for this specific workspace
104+
// Check if user has workspace admin permission (system admins pass automatically)
111105
return h.permissionService.HasWorkspacePermission(user.ID, workspaceID, models.PermissionWorkspaceAdmin)
112106
}
113107

internal/handlers/iterations.go

Lines changed: 138 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,50 @@ import (
99
"time"
1010
"windshift/internal/database"
1111
"windshift/internal/models"
12+
"windshift/internal/services"
1213
"windshift/internal/utils"
1314
)
1415

1516
type IterationHandler struct {
16-
db database.Database
17+
db database.Database
18+
permissionService *services.PermissionService
1719
}
1820

19-
func NewIterationHandler(db database.Database) *IterationHandler {
20-
return &IterationHandler{db: db}
21+
func NewIterationHandler(db database.Database, permissionService *services.PermissionService) *IterationHandler {
22+
return &IterationHandler{
23+
db: db,
24+
permissionService: permissionService,
25+
}
2126
}
2227

2328
func (h *IterationHandler) GetAll(w http.ResponseWriter, r *http.Request) {
29+
user, ok := RequireAuth(w, r)
30+
if !ok {
31+
return
32+
}
33+
2434
// Parse query parameters
2535
workspaceID := r.URL.Query().Get("workspace_id")
2636
typeID := r.URL.Query().Get("type_id")
2737
status := r.URL.Query().Get("status")
2838
includeGlobal := r.URL.Query().Get("include_global") != "false" // Default to true
2939

40+
// Check workspace permission if workspace_id is specified
41+
if workspaceID != "" {
42+
if wsID, err := strconv.Atoi(workspaceID); err == nil {
43+
if !RequireWorkspacePermission(w, user.ID, wsID, models.PermissionItemView, h.permissionService) {
44+
return
45+
}
46+
}
47+
} else {
48+
// For global-only iterations, check global iteration permission
49+
hasGlobalPerm, err := h.permissionService.HasGlobalPermission(user.ID, models.PermissionIterationManage)
50+
if err != nil || !hasGlobalPerm {
51+
http.Error(w, "Forbidden", http.StatusForbidden)
52+
return
53+
}
54+
}
55+
3056
query := `
3157
SELECT i.id, i.name, i.description, i.start_date, i.end_date, i.status,
3258
i.type_id, i.is_global, i.workspace_id, i.created_at, i.updated_at,
@@ -118,6 +144,11 @@ func (h *IterationHandler) GetAll(w http.ResponseWriter, r *http.Request) {
118144
}
119145

120146
func (h *IterationHandler) Get(w http.ResponseWriter, r *http.Request) {
147+
user, ok := RequireAuth(w, r)
148+
if !ok {
149+
return
150+
}
151+
121152
id, ok := requireIDParam(w, r, "id")
122153
if !ok {
123154
return
@@ -153,6 +184,19 @@ func (h *IterationHandler) Get(w http.ResponseWriter, r *http.Request) {
153184
return
154185
}
155186

187+
// Check permission based on whether iteration is global or workspace-scoped
188+
if iteration.IsGlobal {
189+
hasGlobalPerm, err := h.permissionService.HasGlobalPermission(user.ID, models.PermissionIterationManage)
190+
if err != nil || !hasGlobalPerm {
191+
http.Error(w, "Forbidden", http.StatusForbidden)
192+
return
193+
}
194+
} else if workspaceID.Valid {
195+
if !RequireWorkspacePermission(w, user.ID, int(workspaceID.Int64), models.PermissionItemView, h.permissionService) {
196+
return
197+
}
198+
}
199+
156200
iteration.Description = description.String
157201
iteration.TypeID = utils.NullInt64ToPtr(typeID)
158202
iteration.TypeName = typeName.String
@@ -164,6 +208,11 @@ func (h *IterationHandler) Get(w http.ResponseWriter, r *http.Request) {
164208
}
165209

166210
func (h *IterationHandler) Create(w http.ResponseWriter, r *http.Request) {
211+
user, ok := RequireAuth(w, r)
212+
if !ok {
213+
return
214+
}
215+
167216
var iteration models.Iteration
168217
if err := json.NewDecoder(r.Body).Decode(&iteration); err != nil {
169218
http.Error(w, err.Error(), http.StatusBadRequest)
@@ -209,6 +258,19 @@ func (h *IterationHandler) Create(w http.ResponseWriter, r *http.Request) {
209258
return
210259
}
211260

261+
// Check permission based on whether iteration is global or workspace-scoped
262+
if iteration.IsGlobal {
263+
hasGlobalPerm, err := h.permissionService.HasGlobalPermission(user.ID, models.PermissionIterationManage)
264+
if err != nil || !hasGlobalPerm {
265+
http.Error(w, "Forbidden", http.StatusForbidden)
266+
return
267+
}
268+
} else {
269+
if !RequireWorkspacePermission(w, user.ID, *iteration.WorkspaceID, models.PermissionItemEdit, h.permissionService) {
270+
return
271+
}
272+
}
273+
212274
// Validate type_id if provided
213275
if iteration.TypeID != nil {
214276
var typeExists int
@@ -288,6 +350,11 @@ func (h *IterationHandler) Create(w http.ResponseWriter, r *http.Request) {
288350
}
289351

290352
func (h *IterationHandler) Update(w http.ResponseWriter, r *http.Request) {
353+
user, ok := RequireAuth(w, r)
354+
if !ok {
355+
return
356+
}
357+
291358
id, ok := requireIDParam(w, r, "id")
292359
if !ok {
293360
return
@@ -339,6 +406,19 @@ func (h *IterationHandler) Update(w http.ResponseWriter, r *http.Request) {
339406
return
340407
}
341408

409+
// Check permission based on whether iteration is global or workspace-scoped
410+
if iteration.IsGlobal {
411+
hasGlobalPerm, err := h.permissionService.HasGlobalPermission(user.ID, models.PermissionIterationManage)
412+
if err != nil || !hasGlobalPerm {
413+
http.Error(w, "Forbidden", http.StatusForbidden)
414+
return
415+
}
416+
} else {
417+
if !RequireWorkspacePermission(w, user.ID, *iteration.WorkspaceID, models.PermissionItemEdit, h.permissionService) {
418+
return
419+
}
420+
}
421+
342422
// Validate type_id if provided
343423
if iteration.TypeID != nil {
344424
var typeExists int
@@ -418,12 +498,43 @@ func (h *IterationHandler) Update(w http.ResponseWriter, r *http.Request) {
418498
}
419499

420500
func (h *IterationHandler) Delete(w http.ResponseWriter, r *http.Request) {
501+
user, ok := RequireAuth(w, r)
502+
if !ok {
503+
return
504+
}
505+
421506
id, ok := requireIDParam(w, r, "id")
422507
if !ok {
423508
return
424509
}
425510

426-
_, err := h.db.ExecWrite("DELETE FROM iterations WHERE id = ?", id)
511+
// First, fetch the iteration to check its properties for permission validation
512+
var isGlobal bool
513+
var workspaceID sql.NullInt64
514+
err := h.db.QueryRow("SELECT is_global, workspace_id FROM iterations WHERE id = ?", id).Scan(&isGlobal, &workspaceID)
515+
if err == sql.ErrNoRows {
516+
http.Error(w, "Iteration not found", http.StatusNotFound)
517+
return
518+
}
519+
if err != nil {
520+
http.Error(w, err.Error(), http.StatusInternalServerError)
521+
return
522+
}
523+
524+
// Check permission based on whether iteration is global or workspace-scoped
525+
if isGlobal {
526+
hasGlobalPerm, err := h.permissionService.HasGlobalPermission(user.ID, models.PermissionIterationManage)
527+
if err != nil || !hasGlobalPerm {
528+
http.Error(w, "Forbidden", http.StatusForbidden)
529+
return
530+
}
531+
} else if workspaceID.Valid {
532+
if !RequireWorkspacePermission(w, user.ID, int(workspaceID.Int64), models.PermissionItemEdit, h.permissionService) {
533+
return
534+
}
535+
}
536+
537+
_, err = h.db.ExecWrite("DELETE FROM iterations WHERE id = ?", id)
427538
if err != nil {
428539
http.Error(w, err.Error(), http.StatusInternalServerError)
429540
return
@@ -473,25 +584,32 @@ type IterationProgressReport struct {
473584

474585
// GetProgress handles GET /api/iterations/{id}/progress - returns iteration progress report
475586
func (h *IterationHandler) GetProgress(w http.ResponseWriter, r *http.Request) {
587+
user, ok := RequireAuth(w, r)
588+
if !ok {
589+
return
590+
}
591+
476592
iterationID, ok := requireIDParam(w, r, "id")
477593
if !ok {
478594
return
479595
}
480596

481-
// Get iteration details
597+
// Get iteration details including is_global and workspace_id for permission check
482598
var report IterationProgressReport
483599
report.IterationID = iterationID
484600
report.ItemsByCategory = make(map[string][]IterationProgressItem)
485601

486602
var description sql.NullString
487603
var typeColor sql.NullString
604+
var isGlobal bool
605+
var workspaceID sql.NullInt64
488606

489607
err := h.db.QueryRow(`
490-
SELECT i.name, i.description, i.start_date, i.end_date, i.status, it.color
608+
SELECT i.name, i.description, i.start_date, i.end_date, i.status, it.color, i.is_global, i.workspace_id
491609
FROM iterations i
492610
LEFT JOIN iteration_types it ON i.type_id = it.id
493611
WHERE i.id = ?
494-
`, iterationID).Scan(&report.IterationName, &description, &report.StartDate, &report.EndDate, &report.Status, &typeColor)
612+
`, iterationID).Scan(&report.IterationName, &description, &report.StartDate, &report.EndDate, &report.Status, &typeColor, &isGlobal, &workspaceID)
495613

496614
if err == sql.ErrNoRows {
497615
http.Error(w, "Iteration not found", http.StatusNotFound)
@@ -502,6 +620,19 @@ func (h *IterationHandler) GetProgress(w http.ResponseWriter, r *http.Request) {
502620
return
503621
}
504622

623+
// Check permission based on whether iteration is global or workspace-scoped
624+
if isGlobal {
625+
hasGlobalPerm, err := h.permissionService.HasGlobalPermission(user.ID, models.PermissionIterationManage)
626+
if err != nil || !hasGlobalPerm {
627+
http.Error(w, "Forbidden", http.StatusForbidden)
628+
return
629+
}
630+
} else if workspaceID.Valid {
631+
if !RequireWorkspacePermission(w, user.ID, int(workspaceID.Int64), models.PermissionItemView, h.permissionService) {
632+
return
633+
}
634+
}
635+
505636
report.Description = description.String
506637
report.TypeColor = typeColor.String
507638

0 commit comments

Comments
 (0)