mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-09-21 23:51:59 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6c5740f9c | ||
|
|
817b781c88 | ||
|
|
87eaa9e18c |
@@ -311,6 +311,7 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
|||||||
Schema: schema,
|
Schema: schema,
|
||||||
Entity: entity,
|
Entity: entity,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "read",
|
||||||
Options: options,
|
Options: options,
|
||||||
ID: id,
|
ID: id,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
@@ -727,6 +728,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
Schema: schema,
|
Schema: schema,
|
||||||
Entity: entity,
|
Entity: entity,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "create",
|
||||||
Options: options,
|
Options: options,
|
||||||
Data: v,
|
Data: v,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
@@ -774,6 +776,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
Schema: schema,
|
Schema: schema,
|
||||||
Entity: entity,
|
Entity: entity,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "create",
|
||||||
Options: options,
|
Options: options,
|
||||||
Data: v,
|
Data: v,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
@@ -856,6 +859,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
Schema: schema,
|
Schema: schema,
|
||||||
Entity: entity,
|
Entity: entity,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "create",
|
||||||
Options: options,
|
Options: options,
|
||||||
Data: item,
|
Data: item,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
@@ -903,6 +907,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
Schema: schema,
|
Schema: schema,
|
||||||
Entity: entity,
|
Entity: entity,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "create",
|
||||||
Options: options,
|
Options: options,
|
||||||
Data: item,
|
Data: item,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
@@ -987,6 +992,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
Schema: schema,
|
Schema: schema,
|
||||||
Entity: entity,
|
Entity: entity,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "create",
|
||||||
Options: options,
|
Options: options,
|
||||||
Data: itemMap,
|
Data: itemMap,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
@@ -1040,6 +1046,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
Schema: schema,
|
Schema: schema,
|
||||||
Entity: entity,
|
Entity: entity,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "create",
|
||||||
Options: options,
|
Options: options,
|
||||||
Data: itemMap,
|
Data: itemMap,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
@@ -1171,6 +1178,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url
|
|||||||
Schema: schema,
|
Schema: schema,
|
||||||
Entity: entity,
|
Entity: entity,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "update",
|
||||||
Options: options,
|
Options: options,
|
||||||
ID: urlID,
|
ID: urlID,
|
||||||
Data: updates,
|
Data: updates,
|
||||||
@@ -1392,6 +1400,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url
|
|||||||
Schema: schema,
|
Schema: schema,
|
||||||
Entity: entity,
|
Entity: entity,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "update",
|
||||||
Options: options,
|
Options: options,
|
||||||
ID: itemIDStr,
|
ID: itemIDStr,
|
||||||
Data: item,
|
Data: item,
|
||||||
@@ -1548,6 +1557,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url
|
|||||||
Schema: schema,
|
Schema: schema,
|
||||||
Entity: entity,
|
Entity: entity,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "update",
|
||||||
Options: options,
|
Options: options,
|
||||||
ID: itemIDStr,
|
ID: itemIDStr,
|
||||||
Data: itemMap,
|
Data: itemMap,
|
||||||
@@ -1653,6 +1663,7 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
Schema: schema,
|
Schema: schema,
|
||||||
Entity: entity,
|
Entity: entity,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "delete",
|
||||||
ID: id,
|
ID: id,
|
||||||
Data: data,
|
Data: data,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
|
|||||||
@@ -25,12 +25,18 @@ func RegisterSecurityHooks(handler *Handler, securityList *security.SecurityList
|
|||||||
// Hook 1: BeforeRead - Load security rules
|
// Hook 1: BeforeRead - Load security rules
|
||||||
handler.Hooks().Register(BeforeRead, func(hookCtx *HookContext) error {
|
handler.Hooks().Register(BeforeRead, func(hookCtx *HookContext) error {
|
||||||
secCtx := newSecurityContext(hookCtx)
|
secCtx := newSecurityContext(hookCtx)
|
||||||
|
if security.IsModelSecurityDisabled(secCtx) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return security.LoadSecurityRules(secCtx, securityList)
|
return security.LoadSecurityRules(secCtx, securityList)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Hook 2: BeforeScan - Apply row-level security filters
|
// Hook 2: BeforeScan - Apply row-level security filters
|
||||||
handler.Hooks().Register(BeforeScan, func(hookCtx *HookContext) error {
|
handler.Hooks().Register(BeforeScan, func(hookCtx *HookContext) error {
|
||||||
secCtx := newSecurityContext(hookCtx)
|
secCtx := newSecurityContext(hookCtx)
|
||||||
|
if security.ShouldSkipRowSecurity(secCtx, hookCtx.Operation) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return security.ApplyRowSecurity(secCtx, securityList)
|
return security.ApplyRowSecurity(secCtx, securityList)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -97,6 +103,10 @@ func (s *securityContext) GetEntity() string {
|
|||||||
return s.ctx.Entity
|
return s.ctx.Entity
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *securityContext) GetOperation() string {
|
||||||
|
return s.ctx.Operation
|
||||||
|
}
|
||||||
|
|
||||||
func (s *securityContext) GetModel() interface{} {
|
func (s *securityContext) GetModel() interface{} {
|
||||||
return s.ctx.Model
|
return s.ctx.Model
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -379,6 +379,7 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
|||||||
Entity: entity,
|
Entity: entity,
|
||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "read",
|
||||||
Options: options,
|
Options: options,
|
||||||
ID: id,
|
ID: id,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
@@ -1236,6 +1237,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
Entity: entity,
|
Entity: entity,
|
||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "create",
|
||||||
Options: options,
|
Options: options,
|
||||||
Data: data,
|
Data: data,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
@@ -1335,6 +1337,7 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, dat
|
|||||||
Entity: entity,
|
Entity: entity,
|
||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "create",
|
||||||
Options: options,
|
Options: options,
|
||||||
Data: modelValue,
|
Data: modelValue,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
@@ -1489,6 +1492,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id
|
|||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
Tx: tx,
|
Tx: tx,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "update",
|
||||||
Options: options,
|
Options: options,
|
||||||
ID: id,
|
ID: id,
|
||||||
Data: dataMap,
|
Data: dataMap,
|
||||||
@@ -1686,6 +1690,7 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
Entity: entity,
|
Entity: entity,
|
||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "delete",
|
||||||
ID: itemID,
|
ID: itemID,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
Tx: tx,
|
Tx: tx,
|
||||||
@@ -1760,6 +1765,7 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
Entity: entity,
|
Entity: entity,
|
||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "delete",
|
||||||
ID: itemIDStr,
|
ID: itemIDStr,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
Tx: tx,
|
Tx: tx,
|
||||||
@@ -1818,6 +1824,7 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
Entity: entity,
|
Entity: entity,
|
||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "delete",
|
||||||
ID: itemIDStr,
|
ID: itemIDStr,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
Tx: tx,
|
Tx: tx,
|
||||||
@@ -1902,6 +1909,7 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
Entity: entity,
|
Entity: entity,
|
||||||
TableName: tableName,
|
TableName: tableName,
|
||||||
Model: model,
|
Model: model,
|
||||||
|
Operation: "delete",
|
||||||
ID: id,
|
ID: id,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
Tx: h.db,
|
Tx: h.db,
|
||||||
|
|||||||
+54
-12
@@ -232,9 +232,37 @@ func LoadSecurityRules(secCtx SecurityContext, securityList *SecurityList) error
|
|||||||
// ApplyRowSecurity is a public wrapper for applyRowSecurity that accepts a SecurityContext
|
// ApplyRowSecurity is a public wrapper for applyRowSecurity that accepts a SecurityContext
|
||||||
// This allows other packages to apply row-level security using the generic interface
|
// This allows other packages to apply row-level security using the generic interface
|
||||||
func ApplyRowSecurity(secCtx SecurityContext, securityList *SecurityList) error {
|
func ApplyRowSecurity(secCtx SecurityContext, securityList *SecurityList) error {
|
||||||
|
// Spec adapters that expose the dispatched operation can enforce the same
|
||||||
|
// model-rule bypass even when ApplyRowSecurity is called directly.
|
||||||
|
if operationCtx, ok := secCtx.(interface{ GetOperation() string }); ok &&
|
||||||
|
ShouldSkipRowSecurity(secCtx, operationCtx.GetOperation()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return applyRowSecurity(secCtx, securityList)
|
return applyRowSecurity(secCtx, securityList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ShouldSkipRowSecurity reports whether row-security enforcement should be
|
||||||
|
// skipped for the operation. It uses the same model-rule resolution as
|
||||||
|
// CheckModelAuthAllowed so the model registry remains the single source of
|
||||||
|
// truth for security behavior.
|
||||||
|
func ShouldSkipRowSecurity(secCtx SecurityContext, operation string) bool {
|
||||||
|
rules, ok := resolveModelRules(secCtx)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules.SecurityDisabled || (operation == "read" && rules.CanPublicRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsModelSecurityDisabled reports whether all model-level security processing
|
||||||
|
// is disabled for the model. This is distinct from ShouldSkipRowSecurity:
|
||||||
|
// CanPublicRead skips row filtering for reads but must still allow other read
|
||||||
|
// security, such as column masking, to be loaded.
|
||||||
|
func IsModelSecurityDisabled(secCtx SecurityContext) bool {
|
||||||
|
rules, ok := resolveModelRules(secCtx)
|
||||||
|
return ok && rules.SecurityDisabled
|
||||||
|
}
|
||||||
|
|
||||||
// ApplyColumnSecurity is a public wrapper for applyColumnSecurity that accepts a SecurityContext
|
// ApplyColumnSecurity is a public wrapper for applyColumnSecurity that accepts a SecurityContext
|
||||||
// This allows other packages to apply column-level security using the generic interface
|
// This allows other packages to apply column-level security using the generic interface
|
||||||
func ApplyColumnSecurity(secCtx SecurityContext, securityList *SecurityList) error {
|
func ApplyColumnSecurity(secCtx SecurityContext, securityList *SecurityList) error {
|
||||||
@@ -303,18 +331,8 @@ func checkModelDeleteAllowed(secCtx SecurityContext) error {
|
|||||||
// 7. Guest (UserID == 0) → return "authentication required".
|
// 7. Guest (UserID == 0) → return "authentication required".
|
||||||
// 8. Authenticated user → allow (operation-specific checks remain in BeforeUpdate/BeforeDelete).
|
// 8. Authenticated user → allow (operation-specific checks remain in BeforeUpdate/BeforeDelete).
|
||||||
func CheckModelAuthAllowed(secCtx SecurityContext, operation string) error {
|
func CheckModelAuthAllowed(secCtx SecurityContext, operation string) error {
|
||||||
rules, ok := GetModelRulesFromContext(secCtx.GetContext())
|
rules, ok := resolveModelRules(secCtx)
|
||||||
if !ok {
|
if !ok {
|
||||||
schema := secCtx.GetSchema()
|
|
||||||
entity := secCtx.GetEntity()
|
|
||||||
var err error
|
|
||||||
if schema != "" {
|
|
||||||
rules, err = modelregistry.GetModelRulesByName(fmt.Sprintf("%s.%s", schema, entity))
|
|
||||||
}
|
|
||||||
if err != nil || schema == "" {
|
|
||||||
rules, err = modelregistry.GetModelRulesByName(entity)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
// Model not registered - fall through to auth check
|
// Model not registered - fall through to auth check
|
||||||
userID, _ := secCtx.GetUserID()
|
userID, _ := secCtx.GetUserID()
|
||||||
if userID == 0 {
|
if userID == 0 {
|
||||||
@@ -322,7 +340,6 @@ func CheckModelAuthAllowed(secCtx SecurityContext, operation string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if rules.SecurityDisabled {
|
if rules.SecurityDisabled {
|
||||||
return nil
|
return nil
|
||||||
@@ -347,6 +364,31 @@ func CheckModelAuthAllowed(secCtx SecurityContext, operation string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resolveModelRules returns model rules from the request context first, then
|
||||||
|
// falls back to the schema-qualified and unqualified registry names.
|
||||||
|
func resolveModelRules(secCtx SecurityContext) (modelregistry.ModelRules, bool) {
|
||||||
|
if rules, ok := GetModelRulesFromContext(secCtx.GetContext()); ok {
|
||||||
|
return rules, true
|
||||||
|
}
|
||||||
|
|
||||||
|
schema := secCtx.GetSchema()
|
||||||
|
entity := secCtx.GetEntity()
|
||||||
|
var err error
|
||||||
|
if schema != "" {
|
||||||
|
var rules modelregistry.ModelRules
|
||||||
|
rules, err = modelregistry.GetModelRulesByName(fmt.Sprintf("%s.%s", schema, entity))
|
||||||
|
if err == nil {
|
||||||
|
return rules, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rules, err := modelregistry.GetModelRulesByName(entity)
|
||||||
|
if err != nil {
|
||||||
|
return modelregistry.ModelRules{}, false
|
||||||
|
}
|
||||||
|
return rules, true
|
||||||
|
}
|
||||||
|
|
||||||
// CheckModelUpdateAllowed is the public wrapper for checkModelUpdateAllowed.
|
// CheckModelUpdateAllowed is the public wrapper for checkModelUpdateAllowed.
|
||||||
func CheckModelUpdateAllowed(secCtx SecurityContext) error {
|
func CheckModelUpdateAllowed(secCtx SecurityContext) error {
|
||||||
return checkModelUpdateAllowed(secCtx)
|
return checkModelUpdateAllowed(secCtx)
|
||||||
|
|||||||
Reference in New Issue
Block a user