// 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 }