138 lines
3.9 KiB
Go
138 lines
3.9 KiB
Go
// Licensed to the Apache Software Foundation (ASF) under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The ASF licenses this file
|
|
// to you under the Apache License, Version 2.0 (the
|
|
// "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package casbin
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/casbin/casbin/v3/model"
|
|
"github.com/casbin/casbin/v3/persist"
|
|
)
|
|
|
|
// ConflictError represents a transaction conflict error.
|
|
type ConflictError struct {
|
|
Operation persist.PolicyOperation
|
|
Reason string
|
|
}
|
|
|
|
func (e *ConflictError) Error() string {
|
|
return fmt.Sprintf("transaction conflict: %s for operation %v", e.Reason, e.Operation)
|
|
}
|
|
|
|
// ConflictDetector detects conflicts between transaction operations and current model state.
|
|
type ConflictDetector struct {
|
|
baseModel model.Model // Model snapshot when transaction started
|
|
currentModel model.Model // Current model state
|
|
operations []persist.PolicyOperation // Operations to be applied
|
|
}
|
|
|
|
// NewConflictDetector creates a new conflict detector instance.
|
|
func NewConflictDetector(baseModel, currentModel model.Model, operations []persist.PolicyOperation) *ConflictDetector {
|
|
return &ConflictDetector{
|
|
baseModel: baseModel,
|
|
currentModel: currentModel,
|
|
operations: operations,
|
|
}
|
|
}
|
|
|
|
// DetectConflicts checks for conflicts between the transaction operations and current model state.
|
|
// Returns nil if no conflicts are found, otherwise returns a ConflictError describing the conflict.
|
|
func (cd *ConflictDetector) DetectConflicts() error {
|
|
for _, op := range cd.operations {
|
|
var err error
|
|
switch op.Type {
|
|
case persist.OperationAdd:
|
|
// Add operations never conflict
|
|
continue
|
|
|
|
case persist.OperationRemove:
|
|
err = cd.detectRemoveConflict(op)
|
|
|
|
case persist.OperationUpdate:
|
|
err = cd.detectUpdateConflict(op)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// detectRemoveConflict checks for conflicts in remove operations.
|
|
func (cd *ConflictDetector) detectRemoveConflict(op persist.PolicyOperation) error {
|
|
for _, rule := range op.Rules {
|
|
// Check if policy existed in base model
|
|
baseHasPolicy, err := cd.baseModel.HasPolicy(op.Section, op.PolicyType, rule)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !baseHasPolicy {
|
|
continue // Policy didn't exist when transaction started
|
|
}
|
|
|
|
// Check if policy still exists in current model
|
|
currentHasPolicy, err := cd.currentModel.HasPolicy(op.Section, op.PolicyType, rule)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !currentHasPolicy {
|
|
return &ConflictError{
|
|
Operation: op,
|
|
Reason: "policy has been removed by another transaction",
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// detectUpdateConflict checks for conflicts in update operations.
|
|
func (cd *ConflictDetector) detectUpdateConflict(op persist.PolicyOperation) error {
|
|
for i, oldRule := range op.OldRules {
|
|
if i >= len(op.Rules) {
|
|
break
|
|
}
|
|
newRule := op.Rules[i]
|
|
|
|
// Check if old policy still exists
|
|
oldExists, err := cd.currentModel.HasPolicy(op.Section, op.PolicyType, oldRule)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !oldExists {
|
|
return &ConflictError{
|
|
Operation: op,
|
|
Reason: "policy to be updated no longer exists",
|
|
}
|
|
}
|
|
|
|
// Check if new policy already exists
|
|
newExists, err := cd.currentModel.HasPolicy(op.Section, op.PolicyType, newRule)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if newExists {
|
|
return &ConflictError{
|
|
Operation: op,
|
|
Reason: "target policy already exists",
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|