blob: f0f96453dae643e4d68cc62d79a7b00d8f3a5775 [file]
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed 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 casbinraft
import (
"bufio"
"bytes"
"errors"
"strings"
"sync"
"sync/atomic"
"github.com/casbin/casbin/v3"
"github.com/casbin/casbin/v3/persist"
"github.com/casbin/casbin/v3/util"
)
const (
addCommand = iota
removeCommand
removeFilteredCommand
clearCommand
)
// Engine is a wapper for casbin enforcer
type Engine struct {
enforcer *casbin.Enforcer
isLeader uint32
mutex sync.Mutex
}
// Command represents an instruction to change the state of the engine
type Command struct {
Op int `json:"op"`
Sec string `json:"sec"`
Ptype string `json:"ptype"`
Rules [][]string `json:"rules"`
FiledIndex int `json:"filed_index"`
FiledValues []string `json:"filed_values"`
}
func newEngine(enforcer *casbin.Enforcer) *Engine {
return &Engine{
enforcer: enforcer,
}
}
// Apply applies a Raft log entry to the casbin engine.
func (e *Engine) Apply(c Command) {
e.mutex.Lock()
defer e.mutex.Unlock()
shouldPersist := atomic.LoadUint32(&e.isLeader) == 1
switch c.Op {
case addCommand:
_, _, err := e.enforcer.GetPolicyManager().AddPolicies(c.Sec, c.Ptype, c.Rules, shouldPersist)
if err != nil {
// need a way to notify the caller, panic temporarily, the same as following
panic(err)
}
case removeCommand:
_, _, err := e.enforcer.GetPolicyManager().RemovePolicies(c.Sec, c.Ptype, c.Rules, shouldPersist)
if err != nil {
panic(err)
}
case removeFilteredCommand:
_, _, err := e.enforcer.GetPolicyManager().RemoveFilteredPolicy(c.Sec, c.Ptype, shouldPersist, c.FiledIndex, c.FiledValues...)
if err != nil {
panic(err)
}
case clearCommand:
e.enforcer.GetModel().ClearPolicy()
if atomic.LoadUint32(&e.isLeader) == 1 {
err := e.enforcer.SavePolicy()
if err != nil {
panic(err)
}
}
default:
panic(errors.New("wrong command option"))
}
}
// getSnapshot convert model data to snapshot
func (e *Engine) getSnapshot() ([]byte, error) {
var tmp bytes.Buffer
model := e.enforcer.GetModel()
ptypes := model.GetPtypes("p")
for _, ptype := range ptypes {
for _, rule := range model.GetPolicy("p", ptype) {
tmp.WriteString(ptype + ", ")
tmp.WriteString(util.ArrayToString(rule))
tmp.WriteString("\n")
}
}
ptypes = model.GetPtypes("g")
for _, ptype := range ptypes {
for _, rule := range model.GetPolicy("g", ptype) {
tmp.WriteString(ptype + ", ")
tmp.WriteString(util.ArrayToString(rule))
tmp.WriteString("\n")
}
}
return bytes.TrimRight(tmp.Bytes(), "\n"), nil
}
// recoverFromSnapshot save the snapshot data to model
func (e *Engine) recoverFromSnapshot(snapshot []byte) error {
e.enforcer.GetModel().ClearPolicy()
model := e.enforcer.GetModel()
scanner := bufio.NewScanner(bytes.NewReader(snapshot))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
persist.LoadPolicyLine(line, model)
}
return scanner.Err()
}