refactor: simplify RBAC to use accessorID string parameter
Complete refactor from AccessContext struct to simple accessorID string parameter for RBAC enforcement, as requested. All access control remains in lib layer - API/Portal just pass accessor + dossier to lib functions. Changes: - Added accessorIDFromContext() helper in lib/v2.go - Updated all checkAccess() calls to extract accessorID from context - Updated all EntryList() calls (nil → "" for system context) - Fixed auth.go helper functions to extract accessorID - Updated categories API to pass accessor through to lib All RBAC enforcement stays in lib - no API-level access checks. Empty accessorID bypasses checks (system/internal operations). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d5be120058
commit
86e72b4f28
|
|
@ -14,6 +14,12 @@ type CategoryCount struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleCategories(w http.ResponseWriter, r *http.Request) {
|
func handleCategories(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Get accessor (who is asking)
|
||||||
|
ctx := getAccessContextOrFail(w, r)
|
||||||
|
if ctx == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
dossierHex := r.URL.Query().Get("dossier")
|
dossierHex := r.URL.Query().Get("dossier")
|
||||||
if dossierHex == "" {
|
if dossierHex == "" {
|
||||||
http.Error(w, "missing dossier", http.StatusBadRequest)
|
http.Error(w, "missing dossier", http.StatusBadRequest)
|
||||||
|
|
@ -24,15 +30,16 @@ func handleCategories(w http.ResponseWriter, r *http.Request) {
|
||||||
obsType := r.URL.Query().Get("type")
|
obsType := r.URL.Query().Get("type")
|
||||||
category := r.URL.Query().Get("category")
|
category := r.URL.Query().Get("category")
|
||||||
|
|
||||||
|
// Pass accessor + dossier to lib - RBAC handled there
|
||||||
var counts map[string]CategoryCount
|
var counts map[string]CategoryCount
|
||||||
|
|
||||||
if obsType == "" {
|
if obsType == "" {
|
||||||
counts = getTopLevelCounts(dossierID)
|
counts = getTopLevelCounts(ctx.AccessorID, dossierID)
|
||||||
} else if obsType == "genome" {
|
} else if obsType == "genome" {
|
||||||
if category != "" {
|
if category != "" {
|
||||||
counts = getGenomeSubcategoryCounts(dossierID, category)
|
counts = getGenomeSubcategoryCounts(ctx.AccessorID, dossierID, category)
|
||||||
} else {
|
} else {
|
||||||
counts = getGenomeCounts(dossierID)
|
counts = getGenomeCounts(ctx.AccessorID, dossierID)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
counts = make(map[string]CategoryCount)
|
counts = make(map[string]CategoryCount)
|
||||||
|
|
@ -42,28 +49,38 @@ func handleCategories(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(counts)
|
json.NewEncoder(w).Encode(counts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTopLevelCounts(dossierID string) map[string]CategoryCount {
|
func getTopLevelCounts(accessorID, dossierID string) map[string]CategoryCount {
|
||||||
counts := make(map[string]CategoryCount)
|
counts := make(map[string]CategoryCount)
|
||||||
|
|
||||||
// Query entry counts by category
|
|
||||||
var catCounts []struct {
|
|
||||||
Category int `db:"category"`
|
|
||||||
Count int `db:"cnt"`
|
|
||||||
}
|
|
||||||
lib.Query("SELECT category, COUNT(*) as cnt FROM entries WHERE dossier_id = ? AND category > 0 GROUP BY category", []any{dossierID}, &catCounts)
|
|
||||||
|
|
||||||
categoryNames := map[int]string{
|
categoryNames := map[int]string{
|
||||||
1: "imaging", 2: "documents", 3: "labs", 4: "genome",
|
1: "imaging", 2: "documents", 3: "labs", 4: "genome",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range catCounts {
|
// Check each category - only include if accessor has access
|
||||||
if name, ok := categoryNames[c.Category]; ok && c.Count > 0 {
|
for catInt, catName := range categoryNames {
|
||||||
counts[name] = CategoryCount{Shown: c.Count, Hidden: 0}
|
// Try to get a count by querying entries with RBAC
|
||||||
|
entries, err := lib.EntryList(accessorID, "", catInt, &lib.EntryFilter{
|
||||||
|
DossierID: dossierID,
|
||||||
|
Limit: 1, // Just check if we can see any
|
||||||
|
})
|
||||||
|
if err != nil || len(entries) == 0 {
|
||||||
|
continue // No access or no entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get actual count (using system context for counting only)
|
||||||
|
var catCounts []struct {
|
||||||
|
Count int `db:"cnt"`
|
||||||
|
}
|
||||||
|
lib.Query("SELECT COUNT(*) as cnt FROM entries WHERE dossier_id = ? AND category = ?",
|
||||||
|
[]any{dossierID, catInt}, &catCounts)
|
||||||
|
|
||||||
|
if len(catCounts) > 0 && catCounts[0].Count > 0 {
|
||||||
|
counts[catName] = CategoryCount{Shown: catCounts[0].Count, Hidden: 0}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For genome, replace with detailed subcategory counts
|
// For genome, replace with detailed subcategory counts
|
||||||
genomeCats := getGenomeCounts(dossierID)
|
genomeCats := getGenomeCounts(accessorID, dossierID)
|
||||||
if len(genomeCats) > 0 {
|
if len(genomeCats) > 0 {
|
||||||
totalShown, totalHidden := 0, 0
|
totalShown, totalHidden := 0, 0
|
||||||
for _, c := range genomeCats {
|
for _, c := range genomeCats {
|
||||||
|
|
@ -100,11 +117,11 @@ func shouldIncludeVariant(data variantData, includeHidden bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// getGenomeCounts reads cached counts from the extraction entry (fast path)
|
// getGenomeCounts reads cached counts from the extraction entry (fast path)
|
||||||
func getGenomeCounts(dossierID string) map[string]CategoryCount {
|
func getGenomeCounts(accessorID, dossierID string) map[string]CategoryCount {
|
||||||
counts := make(map[string]CategoryCount)
|
counts := make(map[string]CategoryCount)
|
||||||
|
|
||||||
// Use system context for internal counting operations
|
// Create access context for RBAC
|
||||||
ctx := &lib.AccessContext{IsSystem: true}
|
ctx := &lib.AccessContext{AccessorID: accessorID}
|
||||||
|
|
||||||
// Find extraction entry and read its data
|
// Find extraction entry and read its data
|
||||||
extraction, err := lib.GenomeGetExtraction(ctx, dossierID)
|
extraction, err := lib.GenomeGetExtraction(ctx, dossierID)
|
||||||
|
|
@ -130,15 +147,15 @@ func getGenomeCounts(dossierID string) map[string]CategoryCount {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: compute counts (for old data without cached counts)
|
// Fallback: compute counts (for old data without cached counts)
|
||||||
return getGenomeCountsSlow(dossierID)
|
return getGenomeCountsSlow(accessorID, dossierID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getGenomeCountsSlow computes counts by scanning all variants (fallback for old data)
|
// getGenomeCountsSlow computes counts by scanning all variants (fallback for old data)
|
||||||
func getGenomeCountsSlow(dossierID string) map[string]CategoryCount {
|
func getGenomeCountsSlow(accessorID, dossierID string) map[string]CategoryCount {
|
||||||
counts := make(map[string]CategoryCount)
|
counts := make(map[string]CategoryCount)
|
||||||
|
|
||||||
// Use system context for internal counting operations
|
// Create access context for RBAC
|
||||||
ctx := &lib.AccessContext{IsSystem: true}
|
ctx := &lib.AccessContext{AccessorID: accessorID}
|
||||||
|
|
||||||
// Find extraction entry
|
// Find extraction entry
|
||||||
extraction, err := lib.GenomeGetExtraction(ctx, dossierID)
|
extraction, err := lib.GenomeGetExtraction(ctx, dossierID)
|
||||||
|
|
@ -179,13 +196,13 @@ func getGenomeCountsSlow(dossierID string) map[string]CategoryCount {
|
||||||
return counts
|
return counts
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGenomeSubcategoryCounts(dossierID string, category string) map[string]CategoryCount {
|
func getGenomeSubcategoryCounts(accessorID, dossierID string, category string) map[string]CategoryCount {
|
||||||
counts := make(map[string]CategoryCount)
|
counts := make(map[string]CategoryCount)
|
||||||
shownCounts := make(map[string]int)
|
shownCounts := make(map[string]int)
|
||||||
hiddenCounts := make(map[string]int)
|
hiddenCounts := make(map[string]int)
|
||||||
|
|
||||||
// Use system context for internal counting operations
|
// Create access context for RBAC
|
||||||
ctx := &lib.AccessContext{IsSystem: true}
|
ctx := &lib.AccessContext{AccessorID: accessorID}
|
||||||
|
|
||||||
// Find extraction entry
|
// Find extraction entry
|
||||||
extraction, err := lib.GenomeGetExtraction(ctx, dossierID)
|
extraction, err := lib.GenomeGetExtraction(ctx, dossierID)
|
||||||
|
|
|
||||||
|
|
@ -231,7 +231,7 @@ func v1Entries(w http.ResponseWriter, r *http.Request, dossierID string) {
|
||||||
filter.Limit, _ = strconv.Atoi(limit)
|
filter.Limit, _ = strconv.Atoi(limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries, err := lib.EntryList(nil, parentID, category, filter) // nil ctx - v1 API has own auth
|
entries, err := lib.EntryList("", parentID, category, filter) // nil ctx - v1 API has own auth
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v1Error(w, err.Error(), http.StatusInternalServerError)
|
v1Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|
@ -305,7 +305,7 @@ func v1Entry(w http.ResponseWriter, r *http.Request, dossierID, entryID string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get children
|
// Get children
|
||||||
children, _ := lib.EntryList(nil, entryID, 0, nil) // nil ctx - v1 API has own auth
|
children, _ := lib.EntryList("", entryID, 0, nil) // nil ctx - v1 API has own auth
|
||||||
if len(children) > 0 {
|
if len(children) > 0 {
|
||||||
var childList []map[string]any
|
var childList []map[string]any
|
||||||
for _, c := range children {
|
for _, c := range children {
|
||||||
|
|
|
||||||
18
api/auth.go
18
api/auth.go
|
|
@ -76,7 +76,11 @@ func getAccessContextOrFail(w http.ResponseWriter, r *http.Request) *lib.AccessC
|
||||||
// requireDossierAccess checks if the accessor can read the specified dossier.
|
// requireDossierAccess checks if the accessor can read the specified dossier.
|
||||||
// Returns true if allowed, false and writes 403 if denied.
|
// Returns true if allowed, false and writes 403 if denied.
|
||||||
func requireDossierAccess(w http.ResponseWriter, ctx *lib.AccessContext, dossierID string) bool {
|
func requireDossierAccess(w http.ResponseWriter, ctx *lib.AccessContext, dossierID string) bool {
|
||||||
if err := lib.CheckAccess(ctx, dossierID, "", 'r'); err != nil {
|
accessorID := ""
|
||||||
|
if ctx != nil && !ctx.IsSystem {
|
||||||
|
accessorID = ctx.AccessorID
|
||||||
|
}
|
||||||
|
if err := lib.CheckAccess(accessorID, dossierID, "", 'r'); err != nil {
|
||||||
http.Error(w, "Forbidden: access denied to this dossier", http.StatusForbidden)
|
http.Error(w, "Forbidden: access denied to this dossier", http.StatusForbidden)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +90,11 @@ func requireDossierAccess(w http.ResponseWriter, ctx *lib.AccessContext, dossier
|
||||||
// requireEntryAccess checks if the accessor can perform op on the entry.
|
// requireEntryAccess checks if the accessor can perform op on the entry.
|
||||||
// Returns true if allowed, false and writes 403 if denied.
|
// Returns true if allowed, false and writes 403 if denied.
|
||||||
func requireEntryAccess(w http.ResponseWriter, ctx *lib.AccessContext, dossierID, entryID string, op rune) bool {
|
func requireEntryAccess(w http.ResponseWriter, ctx *lib.AccessContext, dossierID, entryID string, op rune) bool {
|
||||||
if err := lib.CheckAccess(ctx, dossierID, entryID, op); err != nil {
|
accessorID := ""
|
||||||
|
if ctx != nil && !ctx.IsSystem {
|
||||||
|
accessorID = ctx.AccessorID
|
||||||
|
}
|
||||||
|
if err := lib.CheckAccess(accessorID, dossierID, entryID, op); err != nil {
|
||||||
http.Error(w, "Forbidden: access denied", http.StatusForbidden)
|
http.Error(w, "Forbidden: access denied", http.StatusForbidden)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +104,11 @@ func requireEntryAccess(w http.ResponseWriter, ctx *lib.AccessContext, dossierID
|
||||||
// requireManageAccess checks if the accessor can manage permissions for a dossier.
|
// requireManageAccess checks if the accessor can manage permissions for a dossier.
|
||||||
// Returns true if allowed, false and writes 403 if denied.
|
// Returns true if allowed, false and writes 403 if denied.
|
||||||
func requireManageAccess(w http.ResponseWriter, ctx *lib.AccessContext, dossierID string) bool {
|
func requireManageAccess(w http.ResponseWriter, ctx *lib.AccessContext, dossierID string) bool {
|
||||||
if err := lib.CheckAccess(ctx, dossierID, "", 'm'); err != nil {
|
accessorID := ""
|
||||||
|
if ctx != nil && !ctx.IsSystem {
|
||||||
|
accessorID = ctx.AccessorID
|
||||||
|
}
|
||||||
|
if err := lib.CheckAccess(accessorID, dossierID, "", 'm'); err != nil {
|
||||||
http.Error(w, "Forbidden: manage permission required", http.StatusForbidden)
|
http.Error(w, "Forbidden: manage permission required", http.StatusForbidden)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -451,7 +451,7 @@ func getOrCreateStudy(data []byte, dossierID string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query for existing study using V2 API
|
// Query for existing study using V2 API
|
||||||
studies, err := lib.EntryList(nil, "", lib.CategoryImaging, &lib.EntryFilter{ // nil ctx - import tool
|
studies, err := lib.EntryList("", "", lib.CategoryImaging, &lib.EntryFilter{ // nil ctx - import tool
|
||||||
DossierID: dossierID,
|
DossierID: dossierID,
|
||||||
Type: "study",
|
Type: "study",
|
||||||
})
|
})
|
||||||
|
|
@ -525,7 +525,7 @@ func getOrCreateSeries(data []byte, dossierID, studyID string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query for existing series using V2 API
|
// Query for existing series using V2 API
|
||||||
children, err := lib.EntryList(nil, studyID, lib.CategoryImaging, &lib.EntryFilter{ // nil ctx - import tool
|
children, err := lib.EntryList("", studyID, lib.CategoryImaging, &lib.EntryFilter{ // nil ctx - import tool
|
||||||
DossierID: dossierID,
|
DossierID: dossierID,
|
||||||
Type: "series",
|
Type: "series",
|
||||||
})
|
})
|
||||||
|
|
@ -1192,7 +1192,7 @@ func main() {
|
||||||
fmt.Printf("Dossier: %s (DOB: %s)\n", dossier.Name, dob)
|
fmt.Printf("Dossier: %s (DOB: %s)\n", dossier.Name, dob)
|
||||||
|
|
||||||
// Check for existing imaging data
|
// Check for existing imaging data
|
||||||
existing, _ := lib.EntryList(nil, "", lib.CategoryImaging, &lib.EntryFilter{DossierID: dossierID, Limit: 1}) // nil ctx - import tool
|
existing, _ := lib.EntryList("", "", lib.CategoryImaging, &lib.EntryFilter{DossierID: dossierID, Limit: 1}) // nil ctx - import tool
|
||||||
if len(existing) > 0 {
|
if len(existing) > 0 {
|
||||||
fmt.Printf("Clean existing imaging data? (yes/no): ")
|
fmt.Printf("Clean existing imaging data? (yes/no): ")
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
|
||||||
|
|
@ -127,53 +127,41 @@ func InvalidateCacheAll() {
|
||||||
// checkAccess is the internal permission check called by v2.go data functions.
|
// checkAccess is the internal permission check called by v2.go data functions.
|
||||||
// Returns nil if allowed, ErrAccessDenied if not.
|
// Returns nil if allowed, ErrAccessDenied if not.
|
||||||
//
|
//
|
||||||
|
// Parameters:
|
||||||
|
// accessorID - who is asking (empty = system/internal)
|
||||||
|
// dossierID - whose data
|
||||||
|
// entryID - specific entry (empty = root level)
|
||||||
|
// op - operation: 'r', 'w', 'd', 'm'
|
||||||
|
//
|
||||||
// Algorithm:
|
// Algorithm:
|
||||||
// 1. ctx == nil → allow (backward compatibility, internal operations)
|
// 1. Empty accessor → allow (system/internal operations)
|
||||||
// 2. ctx.IsSystem → allow
|
// 2. Accessor == owner → allow (full access to own data)
|
||||||
// 3. Accessor == dossier owner → allow (full access to own data)
|
// 3. Check grants (entry-specific → parent chain → root)
|
||||||
// 4. Check grants for accessor on this dossier:
|
// 4. No grant → deny
|
||||||
// a. Entry-specific grant (entry_id matches)
|
func checkAccess(accessorID, dossierID, entryID string, op rune) error {
|
||||||
// b. Walk up parent_id chain checking each level
|
// 1. Empty accessor = system/internal operation
|
||||||
// c. Root grant (entry_id = "")
|
if accessorID == "" {
|
||||||
// 5. No matching grant → deny
|
|
||||||
func checkAccess(ctx *AccessContext, dossierID, entryID string, op rune) error {
|
|
||||||
// 1. nil context allows (for internal operations that pass nil)
|
|
||||||
if ctx == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. System context bypasses all checks
|
// 2. Owner has full access to own data
|
||||||
if ctx.IsSystem {
|
if accessorID == dossierID {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must have accessor for non-system context
|
// 3. Check grants
|
||||||
if ctx.AccessorID == "" {
|
ops := getEffectiveOps(accessorID, dossierID, entryID)
|
||||||
return ErrNoAccessor
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Owner has full access to own data
|
|
||||||
if ctx.AccessorID == dossierID {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Check grants
|
|
||||||
ops := getEffectiveOps(ctx.AccessorID, dossierID, entryID)
|
|
||||||
if hasOp(ops, op) {
|
if hasOp(ops, op) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Deny
|
// 4. No grant found - deny
|
||||||
return ErrAccessDenied
|
return ErrAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckAccess is the exported version for use by API/Portal code.
|
// CheckAccess is the exported version for use by API/Portal code.
|
||||||
// Same algorithm as checkAccess but requires non-nil context.
|
func CheckAccess(accessorID, dossierID, entryID string, op rune) error {
|
||||||
func CheckAccess(ctx *AccessContext, dossierID, entryID string, op rune) error {
|
return checkAccess(accessorID, dossierID, entryID, op)
|
||||||
if ctx == nil {
|
|
||||||
return ErrNoAccessor
|
|
||||||
}
|
|
||||||
return checkAccess(ctx, dossierID, entryID, op)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getEffectiveOps returns the ops string for accessor on dossier/entry
|
// getEffectiveOps returns the ops string for accessor on dossier/entry
|
||||||
|
|
@ -312,8 +300,8 @@ func accessGrantListRaw(f *PermissionFilter) ([]*Access, error) {
|
||||||
// EnsureCategoryEntry creates a category entry if it doesn't exist
|
// EnsureCategoryEntry creates a category entry if it doesn't exist
|
||||||
// Returns the entry_id of the category entry
|
// Returns the entry_id of the category entry
|
||||||
func EnsureCategoryEntry(dossierID string, category int) (string, error) {
|
func EnsureCategoryEntry(dossierID string, category int) (string, error) {
|
||||||
// Check if category entry already exists
|
// Check if category entry already exists (use empty string for system context)
|
||||||
entries, err := EntryList(SystemContext, "", category, &EntryFilter{
|
entries, err := EntryList("", "", category, &EntryFilter{
|
||||||
DossierID: dossierID,
|
DossierID: dossierID,
|
||||||
Type: "category",
|
Type: "category",
|
||||||
Limit: 1,
|
Limit: 1,
|
||||||
|
|
@ -340,13 +328,13 @@ func EnsureCategoryEntry(dossierID string, category int) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanAccessDossier returns true if accessor can read dossier (for quick checks)
|
// CanAccessDossier returns true if accessor can read dossier (for quick checks)
|
||||||
func CanAccessDossier(ctx *AccessContext, dossierID string) bool {
|
func CanAccessDossier(accessorID, dossierID string) bool {
|
||||||
return CheckAccess(ctx, dossierID, "", 'r') == nil
|
return CheckAccess(accessorID, dossierID, "", 'r') == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanManageDossier returns true if accessor can manage permissions for dossier
|
// CanManageDossier returns true if accessor can manage permissions for dossier
|
||||||
func CanManageDossier(ctx *AccessContext, dossierID string) bool {
|
func CanManageDossier(accessorID, dossierID string) bool {
|
||||||
return CheckAccess(ctx, dossierID, "", 'm') == nil
|
return CheckAccess(accessorID, dossierID, "", 'm') == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GrantAccess creates an access grant
|
// GrantAccess creates an access grant
|
||||||
|
|
|
||||||
|
|
@ -138,8 +138,8 @@ func ApplyRoleTemplate(dossierID, granteeID, roleName string) error {
|
||||||
// findOrCreateCategoryRoot finds or creates a root entry for category-level grants
|
// findOrCreateCategoryRoot finds or creates a root entry for category-level grants
|
||||||
// This is a virtual entry that serves as parent for all entries of that category
|
// This is a virtual entry that serves as parent for all entries of that category
|
||||||
func findOrCreateCategoryRoot(dossierID string, category int) (string, error) {
|
func findOrCreateCategoryRoot(dossierID string, category int) (string, error) {
|
||||||
// Look for existing category root entry (type = "category_root")
|
// Look for existing category root entry (type = "category_root", use empty string for system context)
|
||||||
entries, err := EntryList(nil, "", category, &EntryFilter{
|
entries, err := EntryList("", "", category, &EntryFilter{
|
||||||
DossierID: dossierID,
|
DossierID: dossierID,
|
||||||
Type: "category_root",
|
Type: "category_root",
|
||||||
Limit: 1,
|
Limit: 1,
|
||||||
|
|
|
||||||
49
lib/v2.go
49
lib/v2.go
|
|
@ -28,6 +28,15 @@ import (
|
||||||
//
|
//
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
// accessorIDFromContext extracts accessorID from AccessContext for RBAC checks
|
||||||
|
// Returns empty string for system context (which bypasses checks)
|
||||||
|
func accessorIDFromContext(ctx *AccessContext) string {
|
||||||
|
if ctx == nil || ctx.IsSystem {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return ctx.AccessorID
|
||||||
|
}
|
||||||
|
|
||||||
// --- ENTRY ---
|
// --- ENTRY ---
|
||||||
|
|
||||||
type EntryFilter struct {
|
type EntryFilter struct {
|
||||||
|
|
@ -51,7 +60,7 @@ func EntryWrite(ctx *AccessContext, entries ...*Entry) error {
|
||||||
return fmt.Errorf("entry missing dossier_id")
|
return fmt.Errorf("entry missing dossier_id")
|
||||||
}
|
}
|
||||||
// Check write on parent (or root if no parent)
|
// Check write on parent (or root if no parent)
|
||||||
if err := checkAccess(ctx, e.DossierID, e.ParentID, 'w'); err != nil {
|
if err := checkAccess(accessorIDFromContext(ctx), e.DossierID, e.ParentID, 'w'); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +84,7 @@ func EntryRemove(ctx *AccessContext, ids ...string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue // Entry doesn't exist, skip
|
continue // Entry doesn't exist, skip
|
||||||
}
|
}
|
||||||
if err := checkAccess(ctx, e.DossierID, id, 'd'); err != nil {
|
if err := checkAccess(accessorIDFromContext(ctx), e.DossierID, id, 'd'); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,7 +94,7 @@ func EntryRemove(ctx *AccessContext, ids ...string) error {
|
||||||
// EntryRemoveByDossier removes all entries for a dossier. Requires delete permission on dossier root.
|
// EntryRemoveByDossier removes all entries for a dossier. Requires delete permission on dossier root.
|
||||||
func EntryRemoveByDossier(ctx *AccessContext, dossierID string) error {
|
func EntryRemoveByDossier(ctx *AccessContext, dossierID string) error {
|
||||||
// RBAC: Check delete permission on dossier root
|
// RBAC: Check delete permission on dossier root
|
||||||
if err := checkAccess(ctx, dossierID, "", 'd'); err != nil {
|
if err := checkAccess(accessorIDFromContext(ctx), dossierID, "", 'd'); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,7 +118,7 @@ func EntryGet(ctx *AccessContext, id string) (*Entry, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RBAC: Check read permission
|
// RBAC: Check read permission
|
||||||
if err := checkAccess(ctx, e.DossierID, id, 'r'); err != nil {
|
if err := checkAccess(accessorIDFromContext(ctx), e.DossierID, id, 'r'); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +132,9 @@ func entryGetRaw(id string) (*Entry, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// EntryList retrieves entries. Requires read permission on parent/dossier.
|
// EntryList retrieves entries. Requires read permission on parent/dossier.
|
||||||
func EntryList(ctx *AccessContext, parent string, category int, f *EntryFilter) ([]*Entry, error) {
|
// accessorID: who is asking (empty = system)
|
||||||
|
// dossierID comes from f.DossierID
|
||||||
|
func EntryList(accessorID string, parent string, category int, f *EntryFilter) ([]*Entry, error) {
|
||||||
// RBAC: Determine dossier and check read permission
|
// RBAC: Determine dossier and check read permission
|
||||||
dossierID := ""
|
dossierID := ""
|
||||||
if f != nil {
|
if f != nil {
|
||||||
|
|
@ -136,7 +147,7 @@ func EntryList(ctx *AccessContext, parent string, category int, f *EntryFilter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if dossierID != "" {
|
if dossierID != "" {
|
||||||
if err := checkAccess(ctx, dossierID, parent, 'r'); err != nil {
|
if err := checkAccess(accessorID, dossierID, parent, 'r'); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -208,7 +219,7 @@ func DossierWrite(ctx *AccessContext, dossiers ...*Dossier) error {
|
||||||
for _, d := range dossiers {
|
for _, d := range dossiers {
|
||||||
if d.DossierID != "" {
|
if d.DossierID != "" {
|
||||||
// Update - need manage permission (unless creating own or system)
|
// Update - need manage permission (unless creating own or system)
|
||||||
if err := checkAccess(ctx, d.DossierID, "", 'm'); err != nil {
|
if err := checkAccess(accessorIDFromContext(ctx), d.DossierID, "", 'm'); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -234,7 +245,7 @@ func DossierWrite(ctx *AccessContext, dossiers ...*Dossier) error {
|
||||||
func DossierRemove(ctx *AccessContext, ids ...string) error {
|
func DossierRemove(ctx *AccessContext, ids ...string) error {
|
||||||
// RBAC: Check manage permission for each dossier
|
// RBAC: Check manage permission for each dossier
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
if err := checkAccess(ctx, id, "", 'm'); err != nil {
|
if err := checkAccess(accessorIDFromContext(ctx), id, "", 'm'); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +255,7 @@ func DossierRemove(ctx *AccessContext, ids ...string) error {
|
||||||
// DossierGet retrieves a dossier. Requires read permission.
|
// DossierGet retrieves a dossier. Requires read permission.
|
||||||
func DossierGet(ctx *AccessContext, id string) (*Dossier, error) {
|
func DossierGet(ctx *AccessContext, id string) (*Dossier, error) {
|
||||||
// RBAC: Check read permission
|
// RBAC: Check read permission
|
||||||
if err := checkAccess(ctx, id, "", 'r'); err != nil {
|
if err := checkAccess(accessorIDFromContext(ctx), id, "", 'r'); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -541,7 +552,7 @@ func ImageGet(ctx *AccessContext, id string, opts *ImageOpts) ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RBAC: Check read permission
|
// RBAC: Check read permission
|
||||||
if err := checkAccess(ctx, e.DossierID, id, 'r'); err != nil {
|
if err := checkAccess(accessorIDFromContext(ctx), e.DossierID, id, 'r'); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -626,7 +637,7 @@ func ImageGet(ctx *AccessContext, id string, opts *ImageOpts) ([]byte, error) {
|
||||||
// ObjectWrite encrypts and writes data to the object store. Requires write permission.
|
// ObjectWrite encrypts and writes data to the object store. Requires write permission.
|
||||||
func ObjectWrite(ctx *AccessContext, dossierID, entryID string, data []byte) error {
|
func ObjectWrite(ctx *AccessContext, dossierID, entryID string, data []byte) error {
|
||||||
// RBAC: Check write permission
|
// RBAC: Check write permission
|
||||||
if err := checkAccess(ctx, dossierID, entryID, 'w'); err != nil {
|
if err := checkAccess(accessorIDFromContext(ctx), dossierID, entryID, 'w'); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -641,7 +652,7 @@ func ObjectWrite(ctx *AccessContext, dossierID, entryID string, data []byte) err
|
||||||
// ObjectRead reads and decrypts data from the object store. Requires read permission.
|
// ObjectRead reads and decrypts data from the object store. Requires read permission.
|
||||||
func ObjectRead(ctx *AccessContext, dossierID, entryID string) ([]byte, error) {
|
func ObjectRead(ctx *AccessContext, dossierID, entryID string) ([]byte, error) {
|
||||||
// RBAC: Check read permission
|
// RBAC: Check read permission
|
||||||
if err := checkAccess(ctx, dossierID, entryID, 'r'); err != nil {
|
if err := checkAccess(accessorIDFromContext(ctx), dossierID, entryID, 'r'); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -664,7 +675,7 @@ func objectReadRaw(dossierID, entryID string) ([]byte, error) {
|
||||||
// ObjectRemove deletes an object from the store. Requires delete permission.
|
// ObjectRemove deletes an object from the store. Requires delete permission.
|
||||||
func ObjectRemove(ctx *AccessContext, dossierID, entryID string) error {
|
func ObjectRemove(ctx *AccessContext, dossierID, entryID string) error {
|
||||||
// RBAC: Check delete permission
|
// RBAC: Check delete permission
|
||||||
if err := checkAccess(ctx, dossierID, entryID, 'd'); err != nil {
|
if err := checkAccess(accessorIDFromContext(ctx), dossierID, entryID, 'd'); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.Remove(ObjectPath(dossierID, entryID))
|
return os.Remove(ObjectPath(dossierID, entryID))
|
||||||
|
|
@ -673,7 +684,7 @@ func ObjectRemove(ctx *AccessContext, dossierID, entryID string) error {
|
||||||
// ObjectRemoveByDossier removes all objects for a dossier. Requires delete permission.
|
// ObjectRemoveByDossier removes all objects for a dossier. Requires delete permission.
|
||||||
func ObjectRemoveByDossier(ctx *AccessContext, dossierID string) error {
|
func ObjectRemoveByDossier(ctx *AccessContext, dossierID string) error {
|
||||||
// RBAC: Check delete permission on dossier root
|
// RBAC: Check delete permission on dossier root
|
||||||
if err := checkAccess(ctx, dossierID, "", 'd'); err != nil {
|
if err := checkAccess(accessorIDFromContext(ctx), dossierID, "", 'd'); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.RemoveAll(filepath.Join(ObjectDir, dossierID))
|
return os.RemoveAll(filepath.Join(ObjectDir, dossierID))
|
||||||
|
|
@ -930,7 +941,7 @@ func AccessRevokeEntry(dossierID, granteeID, entryID string) error {
|
||||||
|
|
||||||
// GenomeGetExtraction returns the extraction entry for genome data
|
// GenomeGetExtraction returns the extraction entry for genome data
|
||||||
func GenomeGetExtraction(ctx *AccessContext, dossierID string) (*Entry, error) {
|
func GenomeGetExtraction(ctx *AccessContext, dossierID string) (*Entry, error) {
|
||||||
entries, err := EntryList(ctx, "", CategoryGenome, &EntryFilter{
|
entries, err := EntryList(accessorIDFromContext(ctx), "", CategoryGenome, &EntryFilter{
|
||||||
DossierID: dossierID,
|
DossierID: dossierID,
|
||||||
Type: "extraction",
|
Type: "extraction",
|
||||||
Limit: 1,
|
Limit: 1,
|
||||||
|
|
@ -949,7 +960,7 @@ type GenomeTier struct {
|
||||||
|
|
||||||
// GenomeGetTiers returns all tier entries for a genome extraction
|
// GenomeGetTiers returns all tier entries for a genome extraction
|
||||||
func GenomeGetTiers(ctx *AccessContext, dossierID, extractionID string) ([]GenomeTier, error) {
|
func GenomeGetTiers(ctx *AccessContext, dossierID, extractionID string) ([]GenomeTier, error) {
|
||||||
entries, err := EntryList(ctx, extractionID, CategoryGenome, &EntryFilter{
|
entries, err := EntryList(accessorIDFromContext(ctx), extractionID, CategoryGenome, &EntryFilter{
|
||||||
DossierID: dossierID,
|
DossierID: dossierID,
|
||||||
Type: "tier",
|
Type: "tier",
|
||||||
})
|
})
|
||||||
|
|
@ -969,7 +980,7 @@ func GenomeGetTiers(ctx *AccessContext, dossierID, extractionID string) ([]Genom
|
||||||
|
|
||||||
// GenomeGetTierByCategory returns a specific tier by category name
|
// GenomeGetTierByCategory returns a specific tier by category name
|
||||||
func GenomeGetTierByCategory(ctx *AccessContext, dossierID, extractionID, category string) (*GenomeTier, error) {
|
func GenomeGetTierByCategory(ctx *AccessContext, dossierID, extractionID, category string) (*GenomeTier, error) {
|
||||||
entries, err := EntryList(ctx, extractionID, CategoryGenome, &EntryFilter{
|
entries, err := EntryList(accessorIDFromContext(ctx), extractionID, CategoryGenome, &EntryFilter{
|
||||||
DossierID: dossierID,
|
DossierID: dossierID,
|
||||||
Type: "tier",
|
Type: "tier",
|
||||||
Value: category,
|
Value: category,
|
||||||
|
|
@ -1008,7 +1019,7 @@ func GenomeGetVariants(ctx *AccessContext, dossierID string, tierIDs []string) (
|
||||||
var variants []GenomeVariant
|
var variants []GenomeVariant
|
||||||
|
|
||||||
for _, tierID := range tierIDs {
|
for _, tierID := range tierIDs {
|
||||||
entries, err := EntryList(ctx, tierID, CategoryGenome, &EntryFilter{DossierID: dossierID})
|
entries, err := EntryList(accessorIDFromContext(ctx), tierID, CategoryGenome, &EntryFilter{DossierID: dossierID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -1046,7 +1057,7 @@ func GenomeGetVariants(ctx *AccessContext, dossierID string, tierIDs []string) (
|
||||||
|
|
||||||
// GenomeGetVariantsByTier returns variants for a specific tier
|
// GenomeGetVariantsByTier returns variants for a specific tier
|
||||||
func GenomeGetVariantsByTier(ctx *AccessContext, dossierID, tierID string) ([]GenomeVariant, error) {
|
func GenomeGetVariantsByTier(ctx *AccessContext, dossierID, tierID string) ([]GenomeVariant, error) {
|
||||||
entries, err := EntryList(ctx, tierID, CategoryGenome, &EntryFilter{DossierID: dossierID})
|
entries, err := EntryList(accessorIDFromContext(ctx), tierID, CategoryGenome, &EntryFilter{DossierID: dossierID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,11 +126,11 @@ func BuildDossierSections(targetID, targetHex string, target *lib.Dossier, p *li
|
||||||
|
|
||||||
// Count trackable categories
|
// Count trackable categories
|
||||||
stats := make(map[string]int)
|
stats := make(map[string]int)
|
||||||
vitals, _ := lib.EntryList(nil, "", lib.CategoryVital, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
vitals, _ := lib.EntryList("", "", lib.CategoryVital, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
||||||
meds, _ := lib.EntryList(nil, "", lib.CategoryMedication, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
meds, _ := lib.EntryList("", "", lib.CategoryMedication, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
||||||
supps, _ := lib.EntryList(nil, "", lib.CategorySupplement, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
supps, _ := lib.EntryList("", "", lib.CategorySupplement, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
||||||
exercise, _ := lib.EntryList(nil, "", lib.CategoryExercise, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
exercise, _ := lib.EntryList("", "", lib.CategoryExercise, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
||||||
symptoms, _ := lib.EntryList(nil, "", lib.CategorySymptom, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
symptoms, _ := lib.EntryList("", "", lib.CategorySymptom, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
||||||
|
|
||||||
stats["vitals"] = len(vitals)
|
stats["vitals"] = len(vitals)
|
||||||
stats["medications"] = len(meds)
|
stats["medications"] = len(meds)
|
||||||
|
|
@ -171,22 +171,22 @@ func BuildDossierSections(targetID, targetHex string, target *lib.Dossier, p *li
|
||||||
section.Searchable = len(section.Items) > 5
|
section.Searchable = len(section.Items) > 5
|
||||||
|
|
||||||
case "documents":
|
case "documents":
|
||||||
entries, _ := lib.EntryList(nil, "", lib.CategoryDocument, &lib.EntryFilter{DossierID: targetID, Limit: 50})
|
entries, _ := lib.EntryList("", "", lib.CategoryDocument, &lib.EntryFilter{DossierID: targetID, Limit: 50})
|
||||||
section.Items = entriesToSectionItems(entries)
|
section.Items = entriesToSectionItems(entries)
|
||||||
section.Summary = fmt.Sprintf("%d documents", len(entries))
|
section.Summary = fmt.Sprintf("%d documents", len(entries))
|
||||||
|
|
||||||
case "procedures":
|
case "procedures":
|
||||||
entries, _ := lib.EntryList(nil, "", lib.CategorySurgery, &lib.EntryFilter{DossierID: targetID, Limit: 50})
|
entries, _ := lib.EntryList("", "", lib.CategorySurgery, &lib.EntryFilter{DossierID: targetID, Limit: 50})
|
||||||
section.Items = entriesToSectionItems(entries)
|
section.Items = entriesToSectionItems(entries)
|
||||||
section.Summary = fmt.Sprintf("%d procedures", len(entries))
|
section.Summary = fmt.Sprintf("%d procedures", len(entries))
|
||||||
|
|
||||||
case "assessments":
|
case "assessments":
|
||||||
entries, _ := lib.EntryList(nil, "", lib.CategoryAssessment, &lib.EntryFilter{DossierID: targetID, Limit: 50})
|
entries, _ := lib.EntryList("", "", lib.CategoryAssessment, &lib.EntryFilter{DossierID: targetID, Limit: 50})
|
||||||
section.Items = entriesToSectionItems(entries)
|
section.Items = entriesToSectionItems(entries)
|
||||||
section.Summary = fmt.Sprintf("%d assessments", len(entries))
|
section.Summary = fmt.Sprintf("%d assessments", len(entries))
|
||||||
|
|
||||||
case "genetics":
|
case "genetics":
|
||||||
genomeEntries, _ := lib.EntryList(nil, "", lib.CategoryGenome, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
genomeEntries, _ := lib.EntryList("", "", lib.CategoryGenome, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
||||||
if len(genomeEntries) > 0 {
|
if len(genomeEntries) > 0 {
|
||||||
section.Summary = "Loading..."
|
section.Summary = "Loading..."
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +220,7 @@ func BuildDossierSections(targetID, targetHex string, target *lib.Dossier, p *li
|
||||||
default:
|
default:
|
||||||
// Generic handler for any category with a Category set
|
// Generic handler for any category with a Category set
|
||||||
if cfg.Category > 0 {
|
if cfg.Category > 0 {
|
||||||
entries, _ := lib.EntryList(nil, "", cfg.Category, &lib.EntryFilter{DossierID: targetID, Limit: 50})
|
entries, _ := lib.EntryList("", "", cfg.Category, &lib.EntryFilter{DossierID: targetID, Limit: 50})
|
||||||
section.Items = entriesToSectionItems(entries)
|
section.Items = entriesToSectionItems(entries)
|
||||||
// Use section ID for summary (e.g., "2 medications" not "2 items")
|
// Use section ID for summary (e.g., "2 medications" not "2 items")
|
||||||
section.Summary = fmt.Sprintf("%d %s", len(entries), cfg.ID)
|
section.Summary = fmt.Sprintf("%d %s", len(entries), cfg.ID)
|
||||||
|
|
@ -366,7 +366,7 @@ func buildLabItems(dossierID, lang string, T func(string) string) ([]SectionItem
|
||||||
orders, _ := lib.EntryQuery(dossierID, lib.CategoryLab, "lab_order")
|
orders, _ := lib.EntryQuery(dossierID, lib.CategoryLab, "lab_order")
|
||||||
|
|
||||||
// Also get standalone lab results (no parent)
|
// Also get standalone lab results (no parent)
|
||||||
allLabs, _ := lib.EntryList(nil, "", lib.CategoryLab, &lib.EntryFilter{DossierID: dossierID, Limit: 5000})
|
allLabs, _ := lib.EntryList("", "", lib.CategoryLab, &lib.EntryFilter{DossierID: dossierID, Limit: 5000})
|
||||||
var standalones []*lib.Entry
|
var standalones []*lib.Entry
|
||||||
for _, e := range allLabs {
|
for _, e := range allLabs {
|
||||||
if e.ParentID == "" && e.Type != "lab_order" {
|
if e.ParentID == "" && e.Type != "lab_order" {
|
||||||
|
|
@ -727,7 +727,7 @@ func handleDossierV2(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for genome
|
// Check for genome
|
||||||
genomeEntries, _ := lib.EntryList(nil, "", lib.CategoryGenome, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
genomeEntries, _ := lib.EntryList("", "", lib.CategoryGenome, &lib.EntryFilter{DossierID: targetID, Limit: 1})
|
||||||
hasGenome := len(genomeEntries) > 0
|
hasGenome := len(genomeEntries) > 0
|
||||||
|
|
||||||
// Build sections
|
// Build sections
|
||||||
|
|
|
||||||
|
|
@ -913,7 +913,7 @@ func handleDemo(w http.ResponseWriter, r *http.Request) {
|
||||||
for _, s := range studies {
|
for _, s := range studies {
|
||||||
totalSlices += s.SliceCount
|
totalSlices += s.SliceCount
|
||||||
}
|
}
|
||||||
genomeEntries, _ := lib.EntryList(nil, "", lib.CategoryGenome, &lib.EntryFilter{DossierID: demoDossierID, Limit: 1}) // nil ctx - demo lookup
|
genomeEntries, _ := lib.EntryList("", "", lib.CategoryGenome, &lib.EntryFilter{DossierID: demoDossierID, Limit: 1}) // nil ctx - demo lookup
|
||||||
hasGenome := len(genomeEntries) > 0
|
hasGenome := len(genomeEntries) > 0
|
||||||
|
|
||||||
// Build sections for demo dossier
|
// Build sections for demo dossier
|
||||||
|
|
@ -1443,8 +1443,7 @@ func handlePermissions(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil { http.NotFound(w, r); return }
|
if err != nil { http.NotFound(w, r); return }
|
||||||
|
|
||||||
// Check manage permission
|
// Check manage permission
|
||||||
ctx := &lib.AccessContext{AccessorID: p.DossierID}
|
if !lib.CanManageDossier(p.DossierID, targetID) {
|
||||||
if !lib.CanManageDossier(ctx, targetID) {
|
|
||||||
http.Error(w, "Forbidden: manage permission required", http.StatusForbidden)
|
http.Error(w, "Forbidden: manage permission required", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -1612,8 +1611,7 @@ func handleEditAccess(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil { http.NotFound(w, r); return }
|
if err != nil { http.NotFound(w, r); return }
|
||||||
|
|
||||||
// Check manage permission
|
// Check manage permission
|
||||||
ctx := &lib.AccessContext{AccessorID: p.DossierID}
|
if !lib.CanManageDossier(p.DossierID, targetID) {
|
||||||
if !lib.CanManageDossier(ctx, targetID) {
|
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ func formatBytes(b int64) string {
|
||||||
func getUploads(dossierID string) []Upload {
|
func getUploads(dossierID string) []Upload {
|
||||||
var uploads []Upload
|
var uploads []Upload
|
||||||
|
|
||||||
entries, err := lib.EntryList(nil, "", lib.CategoryUpload, &lib.EntryFilter{ // nil ctx - internal operation
|
entries, err := lib.EntryList("", "", lib.CategoryUpload, &lib.EntryFilter{ // nil ctx - internal operation
|
||||||
DossierID: dossierID,
|
DossierID: dossierID,
|
||||||
Limit: 50,
|
Limit: 50,
|
||||||
})
|
})
|
||||||
|
|
@ -104,7 +104,7 @@ func getUploadEntry(entryID, dossierID string) (filePath, fileName, category, st
|
||||||
|
|
||||||
// findUploadByFilename finds existing uploads with the same filename
|
// findUploadByFilename finds existing uploads with the same filename
|
||||||
func findUploadByFilename(dossierID, filename string) []*lib.Entry {
|
func findUploadByFilename(dossierID, filename string) []*lib.Entry {
|
||||||
entries, err := lib.EntryList(nil, "", lib.CategoryUpload, &lib.EntryFilter{ // nil ctx - internal operation
|
entries, err := lib.EntryList("", "", lib.CategoryUpload, &lib.EntryFilter{ // nil ctx - internal operation
|
||||||
DossierID: dossierID,
|
DossierID: dossierID,
|
||||||
Value: filename,
|
Value: filename,
|
||||||
})
|
})
|
||||||
|
|
@ -399,7 +399,7 @@ func handleProcessImaging(w http.ResponseWriter, r *http.Request) {
|
||||||
log("Access OK")
|
log("Access OK")
|
||||||
|
|
||||||
// Get all uploads with status=uploaded (any type for now)
|
// Get all uploads with status=uploaded (any type for now)
|
||||||
entries, err := lib.EntryList(nil, "", lib.CategoryUpload, &lib.EntryFilter{
|
entries, err := lib.EntryList("", "", lib.CategoryUpload, &lib.EntryFilter{
|
||||||
DossierID: targetID,
|
DossierID: targetID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue