// 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 ( "sync" "github.com/casbin/casbin/v3/model" "github.com/casbin/casbin/v3/persist" ) // TransactionBuffer holds all policy changes made within a transaction. // It maintains a list of operations and a snapshot of the model state // at the beginning of the transaction. type TransactionBuffer struct { operations []persist.PolicyOperation // Buffered operations modelSnapshot model.Model // Model state at transaction start mutex sync.RWMutex // Protects concurrent access } // NewTransactionBuffer creates a new transaction buffer with a model snapshot. // The snapshot represents the state of the model at the beginning of the transaction. func NewTransactionBuffer(baseModel model.Model) *TransactionBuffer { return &TransactionBuffer{ operations: make([]persist.PolicyOperation, 0), modelSnapshot: baseModel.Copy(), } } // AddOperation adds a policy operation to the buffer. // This operation will be applied when the transaction is committed. func (tb *TransactionBuffer) AddOperation(op persist.PolicyOperation) { tb.mutex.Lock() defer tb.mutex.Unlock() tb.operations = append(tb.operations, op) } // GetOperations returns all buffered operations. // Returns a copy to prevent external modifications. func (tb *TransactionBuffer) GetOperations() []persist.PolicyOperation { tb.mutex.RLock() defer tb.mutex.RUnlock() // Return a copy to prevent external modifications. result := make([]persist.PolicyOperation, len(tb.operations)) copy(result, tb.operations) return result } // Clear removes all buffered operations. // This is typically called after a successful commit or rollback. func (tb *TransactionBuffer) Clear() { tb.mutex.Lock() defer tb.mutex.Unlock() tb.operations = tb.operations[:0] } // GetModelSnapshot returns the model snapshot taken at transaction start. // This represents the original state before any transaction operations. func (tb *TransactionBuffer) GetModelSnapshot() model.Model { tb.mutex.RLock() defer tb.mutex.RUnlock() return tb.modelSnapshot.Copy() } // ApplyOperationsToModel applies all buffered operations to a model and returns the result. // This simulates what the model would look like after all operations are applied. // It's used for validation and preview purposes within the transaction. func (tb *TransactionBuffer) ApplyOperationsToModel(baseModel model.Model) (model.Model, error) { tb.mutex.RLock() defer tb.mutex.RUnlock() resultModel := baseModel.Copy() for _, op := range tb.operations { switch op.Type { case persist.OperationAdd: for _, rule := range op.Rules { if err := resultModel.AddPolicy(op.Section, op.PolicyType, rule); err != nil { return nil, err } } case persist.OperationRemove: for _, rule := range op.Rules { if _, err := resultModel.RemovePolicy(op.Section, op.PolicyType, rule); err != nil { return nil, err } } case persist.OperationUpdate: // For update operations, remove old rules and add new ones. for i, oldRule := range op.OldRules { if i < len(op.Rules) { if _, err := resultModel.RemovePolicy(op.Section, op.PolicyType, oldRule); err != nil { return nil, err } if err := resultModel.AddPolicy(op.Section, op.PolicyType, op.Rules[i]); err != nil { return nil, err } } } } } return resultModel, nil } // HasOperations returns true if there are any buffered operations. func (tb *TransactionBuffer) HasOperations() bool { tb.mutex.RLock() defer tb.mutex.RUnlock() return len(tb.operations) > 0 } // OperationCount returns the number of buffered operations. func (tb *TransactionBuffer) OperationCount() int { tb.mutex.RLock() defer tb.mutex.RUnlock() return len(tb.operations) }