fix(auth): restricted User können nur zugewiesene Screens aufrufen
requireScreenAccess prüft jetzt für Rolle 'restricted' zusätzlich ob ein Eintrag in user_screen_permissions existiert. Tenant-Match allein reichte bisher nicht — restricted User konnten alle Screens des Tenants aufrufen.
This commit is contained in:
parent
3ebeaa70e1
commit
3a0ac13faa
3 changed files with 20 additions and 20 deletions
|
|
@ -112,7 +112,7 @@ func HandleSetScreenOverride(screens *store.ScreenStore, schedules *store.Screen
|
||||||
http.Error(w, "screen not found", http.StatusNotFound)
|
http.Error(w, "screen not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !requireScreenAccess(w, r, screen) {
|
if !requireScreenAccess(w, r, screen, screens) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ func HandleUpdateSchedule(screens *store.ScreenStore, schedules *store.ScreenSch
|
||||||
http.Error(w, "screen not found", http.StatusNotFound)
|
http.Error(w, "screen not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !requireScreenAccess(w, r, screen) {
|
if !requireScreenAccess(w, r, screen, screens) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,10 @@ func renderTemplate(w http.ResponseWriter, t *template.Template, data any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// requireScreenAccess prüft, ob der eingeloggte User Zugriff auf den Screen hat.
|
// requireScreenAccess prüft, ob der eingeloggte User Zugriff auf den Screen hat.
|
||||||
// Admins dürfen alles. Tenant-User dürfen nur Screens ihres eigenen Tenants bearbeiten.
|
// Admins dürfen alles. screen_user dürfen Screens ihres Tenants. restricted User
|
||||||
|
// benötigen zusätzlich einen Eintrag in user_screen_permissions.
|
||||||
// Gibt true zurück wenn Zugriff erlaubt ist; schreibt 403 und gibt false zurück wenn nicht.
|
// Gibt true zurück wenn Zugriff erlaubt ist; schreibt 403 und gibt false zurück wenn nicht.
|
||||||
func requireScreenAccess(w http.ResponseWriter, r *http.Request, screen *store.Screen) bool {
|
func requireScreenAccess(w http.ResponseWriter, r *http.Request, screen *store.Screen, sc *store.ScreenStore) bool {
|
||||||
u := reqcontext.UserFromContext(r.Context())
|
u := reqcontext.UserFromContext(r.Context())
|
||||||
if u == nil {
|
if u == nil {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
|
@ -54,19 +55,18 @@ func requireScreenAccess(w http.ResponseWriter, r *http.Request, screen *store.S
|
||||||
if u.Role == "admin" {
|
if u.Role == "admin" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// Tenant-User: Screen muss zum eigenen Tenant gehören.
|
|
||||||
// Wir vergleichen über TenantSlug→TenantID, aber der Screen hat TenantID.
|
|
||||||
// Da uns der Tenant-Slug des Users bekannt ist und wir keinen TenantStore
|
|
||||||
// hier haben, vergleichen wir TenantID des Screens mit dem user.TenantID-Feld.
|
|
||||||
// store.User hat TenantSlug aber nicht TenantID — deswegen muss der
|
|
||||||
// aufrufende Handler nach GetBySlug bereits die TenantID des Screens bekannt haben.
|
|
||||||
// Wir nutzen u.TenantSlug und vertrauen darauf dass der Screen bereits geladen ist.
|
|
||||||
// Den eigentlichen Vergleich machen wir via TenantID des Screens vs.
|
|
||||||
// dem TenantID-Feld im User (das über reqcontext gespeichert ist).
|
|
||||||
if u.TenantID != "" && u.TenantID != screen.TenantID {
|
if u.TenantID != "" && u.TenantID != screen.TenantID {
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// Restricted User: zusätzlich explizite Screen-Berechtigung prüfen.
|
||||||
|
if u.Role == "restricted" {
|
||||||
|
ok, err := sc.HasUserScreenAccess(r.Context(), u.ID, screen.ID)
|
||||||
|
if err != nil || !ok {
|
||||||
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -370,7 +370,7 @@ func HandleManageUI(
|
||||||
notifier.RequestScreenshot(screen.Slug)
|
notifier.RequestScreenshot(screen.Slug)
|
||||||
|
|
||||||
// K2: Tenant-Isolation — nur eigener Tenant oder Admin.
|
// K2: Tenant-Isolation — nur eigener Tenant oder Admin.
|
||||||
if !requireScreenAccess(w, r, screen) {
|
if !requireScreenAccess(w, r, screen, screens) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -607,7 +607,7 @@ func HandleUploadMediaUI(media *store.MediaStore, screens *store.ScreenStore, up
|
||||||
}
|
}
|
||||||
|
|
||||||
// K2: Tenant-Isolation.
|
// K2: Tenant-Isolation.
|
||||||
if !requireScreenAccess(w, r, screen) {
|
if !requireScreenAccess(w, r, screen, screens) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -694,7 +694,7 @@ func HandleAddItemUI(playlists *store.PlaylistStore, media *store.MediaStore, sc
|
||||||
}
|
}
|
||||||
|
|
||||||
// K2: Tenant-Isolation.
|
// K2: Tenant-Isolation.
|
||||||
if !requireScreenAccess(w, r, screen) {
|
if !requireScreenAccess(w, r, screen, screens) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -760,7 +760,7 @@ func HandleDeleteItemUI(playlists *store.PlaylistStore, screens *store.ScreenSto
|
||||||
http.Error(w, "screen nicht gefunden", http.StatusNotFound)
|
http.Error(w, "screen nicht gefunden", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !requireScreenAccess(w, r, screen) {
|
if !requireScreenAccess(w, r, screen, screens) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -783,7 +783,7 @@ func HandleReorderUI(playlists *store.PlaylistStore, screens *store.ScreenStore,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// K2: Tenant-Isolation.
|
// K2: Tenant-Isolation.
|
||||||
if !requireScreenAccess(w, r, screen) {
|
if !requireScreenAccess(w, r, screen, screens) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
playlist, err := playlists.GetByScreen(r.Context(), screen.ID)
|
playlist, err := playlists.GetByScreen(r.Context(), screen.ID)
|
||||||
|
|
@ -822,7 +822,7 @@ func HandleUpdateItemUI(playlists *store.PlaylistStore, screens *store.ScreenSto
|
||||||
http.Error(w, "screen nicht gefunden", http.StatusNotFound)
|
http.Error(w, "screen nicht gefunden", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !requireScreenAccess(w, r, screen) {
|
if !requireScreenAccess(w, r, screen, screens) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -864,7 +864,7 @@ func HandleDeleteMediaUI(media *store.MediaStore, screens *store.ScreenStore, up
|
||||||
http.Error(w, "screen nicht gefunden", http.StatusNotFound)
|
http.Error(w, "screen nicht gefunden", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !requireScreenAccess(w, r, screen) {
|
if !requireScreenAccess(w, r, screen, screens) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue