snapshot of https://github.com/casbin/casbin.git (master, 2026-09-15; blobs > 131072B omitted)
This commit is contained in:
202
model/assertion.go
Normal file
202
model/assertion.go
Normal file
@@ -0,0 +1,202 @@
|
||||
// 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 model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/casbin/casbin/v3/rbac"
|
||||
)
|
||||
|
||||
// Assertion represents an expression in a section of the model.
|
||||
// For example: r = sub, obj, act.
|
||||
type Assertion struct {
|
||||
Key string
|
||||
Value string
|
||||
Tokens []string
|
||||
ParamsTokens []string
|
||||
Policy [][]string
|
||||
PolicyMap map[string]int
|
||||
RM rbac.RoleManager
|
||||
CondRM rbac.ConditionalRoleManager
|
||||
FieldIndexMap map[string]int
|
||||
FieldIndexMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (ast *Assertion) buildIncrementalRoleLinks(rm rbac.RoleManager, op PolicyOp, rules [][]string) error {
|
||||
ast.RM = rm
|
||||
count := strings.Count(ast.Value, "_")
|
||||
if count < 2 {
|
||||
return errors.New("the number of \"_\" in role definition should be at least 2")
|
||||
}
|
||||
|
||||
for _, rule := range rules {
|
||||
if len(rule) < count {
|
||||
return errors.New("grouping policy elements do not meet role definition")
|
||||
}
|
||||
if len(rule) > count {
|
||||
rule = rule[:count]
|
||||
}
|
||||
switch op {
|
||||
case PolicyAdd:
|
||||
err := rm.AddLink(rule[0], rule[1], rule[2:]...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case PolicyRemove:
|
||||
err := rm.DeleteLink(rule[0], rule[1], rule[2:]...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ast *Assertion) buildRoleLinks(rm rbac.RoleManager) error {
|
||||
ast.RM = rm
|
||||
count := strings.Count(ast.Value, "_")
|
||||
if count < 2 {
|
||||
return errors.New("the number of \"_\" in role definition should be at least 2")
|
||||
}
|
||||
for _, rule := range ast.Policy {
|
||||
if len(rule) < count {
|
||||
return errors.New("grouping policy elements do not meet role definition")
|
||||
}
|
||||
if len(rule) > count {
|
||||
rule = rule[:count]
|
||||
}
|
||||
err := ast.RM.AddLink(rule[0], rule[1], rule[2:]...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ast *Assertion) buildIncrementalConditionalRoleLinks(condRM rbac.ConditionalRoleManager, op PolicyOp, rules [][]string) error {
|
||||
ast.CondRM = condRM
|
||||
count := strings.Count(ast.Value, "_")
|
||||
if count < 2 {
|
||||
return errors.New("the number of \"_\" in role definition should be at least 2")
|
||||
}
|
||||
|
||||
for _, rule := range rules {
|
||||
if len(rule) < count {
|
||||
return errors.New("grouping policy elements do not meet role definition")
|
||||
}
|
||||
if len(rule) > count {
|
||||
rule = rule[:count]
|
||||
}
|
||||
|
||||
var err error
|
||||
domainRule := rule[2:len(ast.Tokens)]
|
||||
|
||||
switch op {
|
||||
case PolicyAdd:
|
||||
err = ast.addConditionalRoleLink(rule, domainRule)
|
||||
case PolicyRemove:
|
||||
err = ast.CondRM.DeleteLink(rule[0], rule[1], rule[2:]...)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ast *Assertion) buildConditionalRoleLinks(condRM rbac.ConditionalRoleManager) error {
|
||||
ast.CondRM = condRM
|
||||
count := strings.Count(ast.Value, "_")
|
||||
if count < 2 {
|
||||
return errors.New("the number of \"_\" in role definition should be at least 2")
|
||||
}
|
||||
for _, rule := range ast.Policy {
|
||||
if len(rule) < count {
|
||||
return errors.New("grouping policy elements do not meet role definition")
|
||||
}
|
||||
if len(rule) > count {
|
||||
rule = rule[:count]
|
||||
}
|
||||
|
||||
domainRule := rule[2:len(ast.Tokens)]
|
||||
|
||||
err := ast.addConditionalRoleLink(rule, domainRule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addConditionalRoleLink adds Link to rbac.ConditionalRoleManager and sets the parameters for LinkConditionFunc.
|
||||
func (ast *Assertion) addConditionalRoleLink(rule []string, domainRule []string) error {
|
||||
var err error
|
||||
if len(domainRule) == 0 {
|
||||
err = ast.CondRM.AddLink(rule[0], rule[1])
|
||||
if err == nil {
|
||||
ast.CondRM.SetLinkConditionFuncParams(rule[0], rule[1], rule[len(ast.Tokens):]...)
|
||||
}
|
||||
} else {
|
||||
domain := domainRule[0]
|
||||
err = ast.CondRM.AddLink(rule[0], rule[1], domain)
|
||||
if err == nil {
|
||||
ast.CondRM.SetDomainLinkConditionFuncParams(rule[0], rule[1], domain, rule[len(ast.Tokens):]...)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (ast *Assertion) copy() *Assertion {
|
||||
tokens := append([]string(nil), ast.Tokens...)
|
||||
policy := make([][]string, len(ast.Policy))
|
||||
|
||||
for i, p := range ast.Policy {
|
||||
policy[i] = append(policy[i], p...)
|
||||
}
|
||||
policyMap := make(map[string]int)
|
||||
for k, v := range ast.PolicyMap {
|
||||
policyMap[k] = v
|
||||
}
|
||||
|
||||
ast.FieldIndexMutex.RLock()
|
||||
fieldIndexMap := make(map[string]int)
|
||||
for k, v := range ast.FieldIndexMap {
|
||||
fieldIndexMap[k] = v
|
||||
}
|
||||
ast.FieldIndexMutex.RUnlock()
|
||||
|
||||
newAst := &Assertion{
|
||||
Key: ast.Key,
|
||||
Value: ast.Value,
|
||||
PolicyMap: policyMap,
|
||||
Tokens: tokens,
|
||||
Policy: policy,
|
||||
FieldIndexMap: fieldIndexMap,
|
||||
ParamsTokens: append([]string(nil), ast.ParamsTokens...),
|
||||
RM: ast.RM,
|
||||
CondRM: ast.CondRM,
|
||||
}
|
||||
|
||||
return newAst
|
||||
}
|
||||
285
model/constraint.go
Normal file
285
model/constraint.go
Normal file
@@ -0,0 +1,285 @@
|
||||
// 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 model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casbin/v3/errors"
|
||||
)
|
||||
|
||||
// ConstraintType represents the type of constraint.
|
||||
type ConstraintType int
|
||||
|
||||
const (
|
||||
ConstraintTypeSOD ConstraintType = iota
|
||||
ConstraintTypeSODMax
|
||||
ConstraintTypeRoleMax
|
||||
ConstraintTypeRolePre
|
||||
)
|
||||
|
||||
// Constraint represents a policy constraint.
|
||||
type Constraint struct {
|
||||
Key string
|
||||
Type ConstraintType
|
||||
Roles []string
|
||||
Role string
|
||||
MaxCount int
|
||||
PreReqRole string
|
||||
}
|
||||
|
||||
var (
|
||||
// Regex patterns for parsing constraints (compiled once at package initialization).
|
||||
sodPattern = regexp.MustCompile(`^sod\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)$`)
|
||||
sodMaxPattern = regexp.MustCompile(`^sodMax\s*\(\s*\[([^\]]+)\]\s*,\s*(\d+)\s*\)$`)
|
||||
roleMaxPattern = regexp.MustCompile(`^roleMax\s*\(\s*"([^"]+)"\s*,\s*(\d+)\s*\)$`)
|
||||
rolePrePattern = regexp.MustCompile(`^rolePre\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"\s*\)$`)
|
||||
)
|
||||
|
||||
// parseRolesArray parses a comma-separated string of quoted role names.
|
||||
func parseRolesArray(rolesStr string) ([]string, error) {
|
||||
var roles []string
|
||||
for _, role := range strings.Split(rolesStr, ",") {
|
||||
role = strings.TrimSpace(role)
|
||||
role = strings.Trim(role, `"`)
|
||||
if role != "" {
|
||||
roles = append(roles, role)
|
||||
}
|
||||
}
|
||||
|
||||
if len(roles) == 0 {
|
||||
return nil, fmt.Errorf("no roles found in role array")
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
// parseConstraint parses a constraint definition string.
|
||||
func parseConstraint(key, value string) (*Constraint, error) {
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
// Try to match sod pattern
|
||||
if matches := sodPattern.FindStringSubmatch(value); matches != nil {
|
||||
return &Constraint{
|
||||
Key: key,
|
||||
Type: ConstraintTypeSOD,
|
||||
Roles: []string{matches[1], matches[2]},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Try to match sodMax pattern
|
||||
if matches := sodMaxPattern.FindStringSubmatch(value); matches != nil {
|
||||
maxCount, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid max count in sodMax: %w", err)
|
||||
}
|
||||
|
||||
roles, err := parseRolesArray(matches[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sodMax: %w", err)
|
||||
}
|
||||
|
||||
return &Constraint{
|
||||
Key: key,
|
||||
Type: ConstraintTypeSODMax,
|
||||
Roles: roles,
|
||||
MaxCount: maxCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Try to match roleMax pattern
|
||||
if matches := roleMaxPattern.FindStringSubmatch(value); matches != nil {
|
||||
maxCount, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid max count in roleMax: %w", err)
|
||||
}
|
||||
return &Constraint{
|
||||
Key: key,
|
||||
Type: ConstraintTypeRoleMax,
|
||||
Role: matches[1],
|
||||
MaxCount: maxCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Try to match rolePre pattern
|
||||
if matches := rolePrePattern.FindStringSubmatch(value); matches != nil {
|
||||
return &Constraint{
|
||||
Key: key,
|
||||
Type: ConstraintTypeRolePre,
|
||||
Role: matches[1],
|
||||
PreReqRole: matches[2],
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unrecognized constraint format: %s", value)
|
||||
}
|
||||
|
||||
// ValidateConstraints validates all constraints against the current policy.
|
||||
func (model Model) ValidateConstraints() error {
|
||||
// Check if constraints exist
|
||||
if len(model["c"]) == 0 {
|
||||
return nil // No constraints to validate
|
||||
}
|
||||
|
||||
// Check if RBAC is enabled
|
||||
if len(model["g"]) == 0 {
|
||||
return errors.ErrConstraintRequiresRBAC
|
||||
}
|
||||
|
||||
// Get grouping policy
|
||||
gAssertion := model["g"]["g"]
|
||||
if gAssertion == nil {
|
||||
return errors.ErrConstraintRequiresRBAC
|
||||
}
|
||||
|
||||
// Validate each constraint
|
||||
for _, assertion := range model["c"] {
|
||||
constraint, err := parseConstraint(assertion.Key, assertion.Value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", errors.ErrConstraintParsingError, err.Error())
|
||||
}
|
||||
|
||||
if err := model.validateConstraint(constraint, gAssertion.Policy); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateConstraint validates a single constraint against the policy.
|
||||
func (model Model) validateConstraint(constraint *Constraint, groupingPolicy [][]string) error {
|
||||
switch constraint.Type {
|
||||
case ConstraintTypeSOD:
|
||||
return model.validateSOD(constraint, groupingPolicy)
|
||||
case ConstraintTypeSODMax:
|
||||
return model.validateSODMax(constraint, groupingPolicy)
|
||||
case ConstraintTypeRoleMax:
|
||||
return model.validateRoleMax(constraint, groupingPolicy)
|
||||
case ConstraintTypeRolePre:
|
||||
return model.validateRolePre(constraint, groupingPolicy)
|
||||
default:
|
||||
return fmt.Errorf("unknown constraint type")
|
||||
}
|
||||
}
|
||||
|
||||
// buildUserRoleMap builds a map of users to their assigned roles from grouping policy.
|
||||
func buildUserRoleMap(groupingPolicy [][]string) map[string]map[string]bool {
|
||||
userRoles := make(map[string]map[string]bool)
|
||||
|
||||
for _, rule := range groupingPolicy {
|
||||
if len(rule) < 2 {
|
||||
continue
|
||||
}
|
||||
user := rule[0]
|
||||
role := rule[1]
|
||||
|
||||
if userRoles[user] == nil {
|
||||
userRoles[user] = make(map[string]bool)
|
||||
}
|
||||
userRoles[user][role] = true
|
||||
}
|
||||
|
||||
return userRoles
|
||||
}
|
||||
|
||||
// validateSOD validates a Separation of Duties constraint.
|
||||
func (model Model) validateSOD(constraint *Constraint, groupingPolicy [][]string) error {
|
||||
if len(constraint.Roles) != 2 {
|
||||
return errors.NewConstraintViolationError(constraint.Key, "sod requires exactly 2 roles")
|
||||
}
|
||||
|
||||
role1, role2 := constraint.Roles[0], constraint.Roles[1]
|
||||
userRoles := buildUserRoleMap(groupingPolicy)
|
||||
|
||||
// Check if any user has both roles
|
||||
for user, roles := range userRoles {
|
||||
if roles[role1] && roles[role2] {
|
||||
return errors.NewConstraintViolationError(constraint.Key,
|
||||
fmt.Sprintf("user '%s' cannot have both roles '%s' and '%s'", user, role1, role2))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSODMax validates a maximum role count constraint for a role set.
|
||||
func (model Model) validateSODMax(constraint *Constraint, groupingPolicy [][]string) error {
|
||||
userRoles := buildUserRoleMap(groupingPolicy)
|
||||
|
||||
// Check if any user has more than maxCount roles from the role set
|
||||
for user, roles := range userRoles {
|
||||
count := 0
|
||||
for _, role := range constraint.Roles {
|
||||
if roles[role] {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > constraint.MaxCount {
|
||||
return errors.NewConstraintViolationError(constraint.Key,
|
||||
fmt.Sprintf("user '%s' has %d roles from %v, exceeds maximum of %d",
|
||||
user, count, constraint.Roles, constraint.MaxCount))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateRoleMax validates a role cardinality constraint.
|
||||
func (model Model) validateRoleMax(constraint *Constraint, groupingPolicy [][]string) error {
|
||||
roleCount := 0
|
||||
|
||||
// Count how many users have this role
|
||||
for _, rule := range groupingPolicy {
|
||||
if len(rule) < 2 {
|
||||
continue
|
||||
}
|
||||
role := rule[1]
|
||||
|
||||
if role == constraint.Role {
|
||||
roleCount++
|
||||
}
|
||||
}
|
||||
|
||||
if roleCount > constraint.MaxCount {
|
||||
return errors.NewConstraintViolationError(constraint.Key,
|
||||
fmt.Sprintf("role '%s' assigned to %d users, exceeds maximum of %d",
|
||||
constraint.Role, roleCount, constraint.MaxCount))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateRolePre validates a prerequisite role constraint.
|
||||
func (model Model) validateRolePre(constraint *Constraint, groupingPolicy [][]string) error {
|
||||
userRoles := buildUserRoleMap(groupingPolicy)
|
||||
|
||||
// Check if any user has the main role without the prerequisite role
|
||||
for user, roles := range userRoles {
|
||||
if roles[constraint.Role] && !roles[constraint.PreReqRole] {
|
||||
return errors.NewConstraintViolationError(constraint.Key,
|
||||
fmt.Sprintf("user '%s' has role '%s' but lacks prerequisite role '%s'",
|
||||
user, constraint.Role, constraint.PreReqRole))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
69
model/function.go
Normal file
69
model/function.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// 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 model
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/casbin/casbin/v3/util"
|
||||
"github.com/casbin/govaluate"
|
||||
)
|
||||
|
||||
// FunctionMap represents the collection of Function.
|
||||
type FunctionMap struct {
|
||||
fns *sync.Map
|
||||
}
|
||||
|
||||
// [string]govaluate.ExpressionFunction
|
||||
|
||||
// AddFunction adds an expression function.
|
||||
func (fm *FunctionMap) AddFunction(name string, function govaluate.ExpressionFunction) {
|
||||
fm.fns.LoadOrStore(name, function)
|
||||
}
|
||||
|
||||
// LoadFunctionMap loads an initial function map.
|
||||
func LoadFunctionMap() FunctionMap {
|
||||
fm := &FunctionMap{}
|
||||
fm.fns = &sync.Map{}
|
||||
|
||||
fm.AddFunction("keyMatch", util.KeyMatchFunc)
|
||||
fm.AddFunction("keyGet", util.KeyGetFunc)
|
||||
fm.AddFunction("keyMatch2", util.KeyMatch2Func)
|
||||
fm.AddFunction("keyGet2", util.KeyGet2Func)
|
||||
fm.AddFunction("keyMatch3", util.KeyMatch3Func)
|
||||
fm.AddFunction("keyGet3", util.KeyGet3Func)
|
||||
fm.AddFunction("keyMatch4", util.KeyMatch4Func)
|
||||
fm.AddFunction("keyMatch5", util.KeyMatch5Func)
|
||||
fm.AddFunction("regexMatch", util.RegexMatchFunc)
|
||||
fm.AddFunction("ipMatch", util.IPMatchFunc)
|
||||
fm.AddFunction("globMatch", util.GlobMatchFunc)
|
||||
|
||||
return *fm
|
||||
}
|
||||
|
||||
// GetFunctions return a map with all the functions.
|
||||
func (fm *FunctionMap) GetFunctions() map[string]govaluate.ExpressionFunction {
|
||||
ret := make(map[string]govaluate.ExpressionFunction)
|
||||
|
||||
fm.fns.Range(func(k interface{}, v interface{}) bool {
|
||||
ret[k.(string)] = v.(govaluate.ExpressionFunction)
|
||||
return true
|
||||
})
|
||||
|
||||
return ret
|
||||
}
|
||||
442
model/model.go
Normal file
442
model/model.go
Normal file
@@ -0,0 +1,442 @@
|
||||
// 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 model
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casbin/v3/config"
|
||||
"github.com/casbin/casbin/v3/constant"
|
||||
"github.com/casbin/casbin/v3/util"
|
||||
)
|
||||
|
||||
// Model represents the whole access control model.
|
||||
type Model map[string]AssertionMap
|
||||
|
||||
// AssertionMap is the collection of assertions, can be "r", "p", "g", "e", "m".
|
||||
type AssertionMap map[string]*Assertion
|
||||
|
||||
const (
|
||||
defaultDomain string = ""
|
||||
defaultSeparator string = "::"
|
||||
)
|
||||
|
||||
var sectionNameMap = map[string]string{
|
||||
"r": "request_definition",
|
||||
"p": "policy_definition",
|
||||
"g": "role_definition",
|
||||
"e": "policy_effect",
|
||||
"m": "matchers",
|
||||
"c": "constraint_definition",
|
||||
}
|
||||
|
||||
// Minimal required sections for a model to be valid.
|
||||
var requiredSections = []string{"r", "p", "e", "m"}
|
||||
|
||||
func loadAssertion(model Model, cfg config.ConfigInterface, sec string, key string) bool {
|
||||
value := cfg.String(sectionNameMap[sec] + "::" + key)
|
||||
return model.AddDef(sec, key, value)
|
||||
}
|
||||
|
||||
var paramsRegex = regexp.MustCompile(`\((.*?)\)`)
|
||||
|
||||
// getParamsToken Get ParamsToken from Assertion.Value.
|
||||
func getParamsToken(value string) []string {
|
||||
paramsString := paramsRegex.FindString(value)
|
||||
if paramsString == "" {
|
||||
return nil
|
||||
}
|
||||
paramsString = strings.TrimSuffix(strings.TrimPrefix(paramsString, "("), ")")
|
||||
return strings.Split(paramsString, ",")
|
||||
}
|
||||
|
||||
// AddDef adds an assertion to the model.
|
||||
func (model Model) AddDef(sec string, key string, value string) bool {
|
||||
if value == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
ast := Assertion{}
|
||||
ast.Key = key
|
||||
ast.Value = value
|
||||
ast.PolicyMap = make(map[string]int)
|
||||
ast.FieldIndexMap = make(map[string]int)
|
||||
|
||||
switch sec {
|
||||
case "r", "p":
|
||||
ast.Tokens = strings.Split(ast.Value, ",")
|
||||
for i := range ast.Tokens {
|
||||
ast.Tokens[i] = key + "_" + strings.TrimSpace(ast.Tokens[i])
|
||||
}
|
||||
case "g":
|
||||
ast.ParamsTokens = getParamsToken(ast.Value)
|
||||
ast.Tokens = strings.Split(ast.Value, ",")
|
||||
ast.Tokens = ast.Tokens[:len(ast.Tokens)-len(ast.ParamsTokens)]
|
||||
default:
|
||||
ast.Value = util.RemoveComments(util.EscapeAssertion(ast.Value))
|
||||
}
|
||||
|
||||
if sec == "m" {
|
||||
// Escape backslashes in string literals to match CSV parsing behavior
|
||||
ast.Value = util.EscapeStringLiterals(ast.Value)
|
||||
|
||||
if strings.Contains(ast.Value, "in") {
|
||||
ast.Value = strings.ReplaceAll(strings.ReplaceAll(ast.Value, "[", "("), "]", ")")
|
||||
}
|
||||
}
|
||||
|
||||
_, ok := model[sec]
|
||||
if !ok {
|
||||
model[sec] = make(AssertionMap)
|
||||
}
|
||||
|
||||
model[sec][key] = &ast
|
||||
return true
|
||||
}
|
||||
|
||||
func getKeySuffix(i int) string {
|
||||
if i == 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strconv.Itoa(i)
|
||||
}
|
||||
|
||||
func loadSection(model Model, cfg config.ConfigInterface, sec string) {
|
||||
i := 1
|
||||
for {
|
||||
if !loadAssertion(model, cfg, sec, sec+getKeySuffix(i)) {
|
||||
break
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewModel creates an empty model.
|
||||
func NewModel() Model {
|
||||
m := make(Model)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// NewModelFromFile creates a model from a .CONF file.
|
||||
func NewModelFromFile(path string) (Model, error) {
|
||||
m := NewModel()
|
||||
|
||||
err := m.LoadModel(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// NewModelFromString creates a model from a string which contains model text.
|
||||
func NewModelFromString(text string) (Model, error) {
|
||||
m := NewModel()
|
||||
|
||||
err := m.LoadModelFromText(text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// LoadModel loads the model from model CONF file.
|
||||
func (model Model) LoadModel(path string) error {
|
||||
cfg, err := config.NewConfig(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return model.loadModelFromConfig(cfg)
|
||||
}
|
||||
|
||||
// LoadModelFromText loads the model from the text.
|
||||
func (model Model) LoadModelFromText(text string) error {
|
||||
cfg, err := config.NewConfigFromText(text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return model.loadModelFromConfig(cfg)
|
||||
}
|
||||
|
||||
// loadModelFromConfig loads the model from a config interface.
|
||||
// It loads all sections defined in sectionNameMap and validates that required sections are present.
|
||||
// If constraint_definition section exists, it validates all constraints against the current policy.
|
||||
// Note: Constraint validation is performed during model loading, which may affect loading performance
|
||||
// and can cause model loading to fail if constraints are violated or invalid.
|
||||
func (model Model) loadModelFromConfig(cfg config.ConfigInterface) error {
|
||||
for s := range sectionNameMap {
|
||||
loadSection(model, cfg, s)
|
||||
}
|
||||
ms := make([]string, 0)
|
||||
for _, rs := range requiredSections {
|
||||
if !model.hasSection(rs) {
|
||||
ms = append(ms, sectionNameMap[rs])
|
||||
}
|
||||
}
|
||||
if len(ms) > 0 {
|
||||
return fmt.Errorf("missing required sections: %s", strings.Join(ms, ","))
|
||||
}
|
||||
|
||||
// Validate constraints after model is loaded
|
||||
if err := model.ValidateConstraints(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (model Model) hasSection(sec string) bool {
|
||||
section := model[sec]
|
||||
return section != nil
|
||||
}
|
||||
|
||||
func (model Model) GetAssertion(sec string, ptype string) (*Assertion, error) {
|
||||
if model[sec] == nil {
|
||||
return nil, fmt.Errorf("missing required section %s", sec)
|
||||
}
|
||||
if model[sec][ptype] == nil {
|
||||
return nil, fmt.Errorf("missing required definition %s in section %s", ptype, sec)
|
||||
}
|
||||
return model[sec][ptype], nil
|
||||
}
|
||||
|
||||
// PrintModel prints the model to the log.
|
||||
func (model Model) PrintModel() {
|
||||
// Logger has been removed - this is now a no-op
|
||||
}
|
||||
|
||||
func (model Model) SortPoliciesBySubjectHierarchy() error {
|
||||
if model["e"]["e"].Value != constant.SubjectPriorityEffect {
|
||||
return nil
|
||||
}
|
||||
g, err := model.GetAssertion("g", "g")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
subIndex := 0
|
||||
for ptype, assertion := range model["p"] {
|
||||
domainIndex, err := model.GetFieldIndex(ptype, constant.DomainIndex)
|
||||
if err != nil {
|
||||
domainIndex = -1
|
||||
}
|
||||
policies := assertion.Policy
|
||||
subjectHierarchyMap, err := getSubjectHierarchyMap(g.Policy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sort.SliceStable(policies, func(i, j int) bool {
|
||||
domain1, domain2 := defaultDomain, defaultDomain
|
||||
if domainIndex != -1 {
|
||||
domain1 = policies[i][domainIndex]
|
||||
domain2 = policies[j][domainIndex]
|
||||
}
|
||||
name1, name2 := getNameWithDomain(domain1, policies[i][subIndex]), getNameWithDomain(domain2, policies[j][subIndex])
|
||||
p1 := subjectHierarchyMap[name1]
|
||||
p2 := subjectHierarchyMap[name2]
|
||||
return p1 > p2
|
||||
})
|
||||
for i, policy := range assertion.Policy {
|
||||
assertion.PolicyMap[strings.Join(policy, ",")] = i
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSubjectHierarchyMap(policies [][]string) (map[string]int, error) {
|
||||
subjectHierarchyMap := make(map[string]int)
|
||||
// Tree structure of role
|
||||
policyMap := make(map[string][]string)
|
||||
for _, policy := range policies {
|
||||
if len(policy) < 2 {
|
||||
return nil, errors.New("policy g expect 2 more params")
|
||||
}
|
||||
domain := defaultDomain
|
||||
if len(policy) != 2 {
|
||||
domain = policy[2]
|
||||
}
|
||||
child := getNameWithDomain(domain, policy[0])
|
||||
parent := getNameWithDomain(domain, policy[1])
|
||||
policyMap[parent] = append(policyMap[parent], child)
|
||||
if _, ok := subjectHierarchyMap[child]; !ok {
|
||||
subjectHierarchyMap[child] = 0
|
||||
}
|
||||
if _, ok := subjectHierarchyMap[parent]; !ok {
|
||||
subjectHierarchyMap[parent] = 0
|
||||
}
|
||||
subjectHierarchyMap[child] = 1
|
||||
}
|
||||
// Use queues for levelOrder
|
||||
queue := list.New()
|
||||
for k, v := range subjectHierarchyMap {
|
||||
root := k
|
||||
if v != 0 {
|
||||
continue
|
||||
}
|
||||
lv := 0
|
||||
queue.PushBack(root)
|
||||
for queue.Len() != 0 {
|
||||
sz := queue.Len()
|
||||
for i := 0; i < sz; i++ {
|
||||
node := queue.Front()
|
||||
queue.Remove(node)
|
||||
nodeValue := node.Value.(string)
|
||||
subjectHierarchyMap[nodeValue] = lv
|
||||
if _, ok := policyMap[nodeValue]; ok {
|
||||
for _, child := range policyMap[nodeValue] {
|
||||
queue.PushBack(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
lv++
|
||||
}
|
||||
}
|
||||
return subjectHierarchyMap, nil
|
||||
}
|
||||
|
||||
func getNameWithDomain(domain string, name string) string {
|
||||
return domain + defaultSeparator + name
|
||||
}
|
||||
|
||||
func (model Model) SortPoliciesByPriority() error {
|
||||
for ptype, assertion := range model["p"] {
|
||||
priorityIndex, err := model.GetFieldIndex(ptype, constant.PriorityIndex)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
policies := assertion.Policy
|
||||
sort.SliceStable(policies, func(i, j int) bool {
|
||||
p1, err := strconv.Atoi(policies[i][priorityIndex])
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
p2, err := strconv.Atoi(policies[j][priorityIndex])
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return p1 < p2
|
||||
})
|
||||
for i, policy := range assertion.Policy {
|
||||
assertion.PolicyMap[strings.Join(policy, ",")] = i
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
pPattern = regexp.MustCompile("^p_")
|
||||
rPattern = regexp.MustCompile("^r_")
|
||||
)
|
||||
|
||||
func (model Model) ToText() string {
|
||||
tokenPatterns := make(map[string]string)
|
||||
|
||||
for _, ptype := range []string{"r", "p"} {
|
||||
for _, token := range model[ptype][ptype].Tokens {
|
||||
tokenPatterns[token] = rPattern.ReplaceAllString(pPattern.ReplaceAllString(token, "p."), "r.")
|
||||
}
|
||||
}
|
||||
if strings.Contains(model["e"]["e"].Value, "p_eft") {
|
||||
tokenPatterns["p_eft"] = "p.eft"
|
||||
}
|
||||
s := strings.Builder{}
|
||||
writeString := func(sec string) {
|
||||
for ptype := range model[sec] {
|
||||
value := model[sec][ptype].Value
|
||||
for tokenPattern, newToken := range tokenPatterns {
|
||||
value = strings.ReplaceAll(value, tokenPattern, newToken)
|
||||
}
|
||||
fmt.Fprintf(&s, "%s = %s\n", sec, value)
|
||||
}
|
||||
}
|
||||
s.WriteString("[request_definition]\n")
|
||||
writeString("r")
|
||||
s.WriteString("[policy_definition]\n")
|
||||
writeString("p")
|
||||
if _, ok := model["g"]; ok {
|
||||
s.WriteString("[role_definition]\n")
|
||||
for ptype := range model["g"] {
|
||||
fmt.Fprintf(&s, "%s = %s\n", ptype, model["g"][ptype].Value)
|
||||
}
|
||||
}
|
||||
if _, ok := model["c"]; ok {
|
||||
s.WriteString("[constraint_definition]\n")
|
||||
for ptype := range model["c"] {
|
||||
fmt.Fprintf(&s, "%s = %s\n", ptype, model["c"][ptype].Value)
|
||||
}
|
||||
}
|
||||
s.WriteString("[policy_effect]\n")
|
||||
writeString("e")
|
||||
s.WriteString("[matchers]\n")
|
||||
writeString("m")
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (model Model) Copy() Model {
|
||||
newModel := NewModel()
|
||||
|
||||
for sec, m := range model {
|
||||
newAstMap := make(AssertionMap)
|
||||
for ptype, ast := range m {
|
||||
newAstMap[ptype] = ast.copy()
|
||||
}
|
||||
newModel[sec] = newAstMap
|
||||
}
|
||||
|
||||
return newModel
|
||||
}
|
||||
|
||||
func (model Model) GetFieldIndex(ptype string, field string) (int, error) {
|
||||
assertion := model["p"][ptype]
|
||||
|
||||
assertion.FieldIndexMutex.RLock()
|
||||
if index, ok := assertion.FieldIndexMap[field]; ok {
|
||||
assertion.FieldIndexMutex.RUnlock()
|
||||
return index, nil
|
||||
}
|
||||
assertion.FieldIndexMutex.RUnlock()
|
||||
|
||||
pattern := fmt.Sprintf("%s_"+field, ptype)
|
||||
index := -1
|
||||
for i, token := range assertion.Tokens {
|
||||
if token == pattern {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index == -1 {
|
||||
return index, fmt.Errorf(field + " index is not set, please use enforcer.SetFieldIndex() to set index")
|
||||
}
|
||||
|
||||
assertion.FieldIndexMutex.Lock()
|
||||
assertion.FieldIndexMap[field] = index
|
||||
assertion.FieldIndexMutex.Unlock()
|
||||
|
||||
return index, nil
|
||||
}
|
||||
164
model/model_test.go
Normal file
164
model/model_test.go
Normal file
@@ -0,0 +1,164 @@
|
||||
// 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 model
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/casbin/casbin/v3/config"
|
||||
"github.com/casbin/casbin/v3/constant"
|
||||
)
|
||||
|
||||
var (
|
||||
basicExample = filepath.Join("..", "examples", "basic_model.conf")
|
||||
basicConfig = &MockConfig{
|
||||
data: map[string]string{
|
||||
"request_definition::r": "sub, obj, act",
|
||||
"policy_definition::p": "sub, obj, act",
|
||||
"policy_effect::e": "some(where (p.eft == allow))",
|
||||
"matchers::m": "r.sub == p.sub && r.obj == p.obj && r.act == p.act",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type MockConfig struct {
|
||||
data map[string]string
|
||||
config.ConfigInterface
|
||||
}
|
||||
|
||||
func (mc *MockConfig) String(key string) string {
|
||||
return mc.data[key]
|
||||
}
|
||||
|
||||
func TestNewModel(t *testing.T) {
|
||||
m := NewModel()
|
||||
if m == nil {
|
||||
t.Error("new model should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewModelFromFile(t *testing.T) {
|
||||
m, err := NewModelFromFile(basicExample)
|
||||
if err != nil {
|
||||
t.Errorf("model failed to load from file: %s", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Error("model should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewModelFromString(t *testing.T) {
|
||||
modelBytes, _ := os.ReadFile(basicExample)
|
||||
modelString := string(modelBytes)
|
||||
m, err := NewModelFromString(modelString)
|
||||
if err != nil {
|
||||
t.Errorf("model failed to load from string: %s", err)
|
||||
}
|
||||
if m == nil {
|
||||
t.Error("model should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadModelFromConfig(t *testing.T) {
|
||||
m := NewModel()
|
||||
err := m.loadModelFromConfig(basicConfig)
|
||||
if err != nil {
|
||||
t.Error("basic config should not return an error")
|
||||
}
|
||||
m = NewModel()
|
||||
err = m.loadModelFromConfig(&MockConfig{})
|
||||
if err == nil {
|
||||
t.Error("empty config should return error")
|
||||
} else {
|
||||
// check for missing sections in message
|
||||
for _, rs := range requiredSections {
|
||||
if !strings.Contains(err.Error(), sectionNameMap[rs]) {
|
||||
t.Errorf("section name: %s should be in message", sectionNameMap[rs])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasSection(t *testing.T) {
|
||||
m := NewModel()
|
||||
_ = m.loadModelFromConfig(basicConfig)
|
||||
for _, sec := range requiredSections {
|
||||
if !m.hasSection(sec) {
|
||||
t.Errorf("%s section was expected in model", sec)
|
||||
}
|
||||
}
|
||||
m = NewModel()
|
||||
_ = m.loadModelFromConfig(&MockConfig{})
|
||||
for _, sec := range requiredSections {
|
||||
if m.hasSection(sec) {
|
||||
t.Errorf("%s section was not expected in model", sec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestModel_AddDef(t *testing.T) {
|
||||
m := NewModel()
|
||||
s := "r"
|
||||
v := "sub, obj, act"
|
||||
ok := m.AddDef(s, s, v)
|
||||
if !ok {
|
||||
t.Errorf("non empty assertion should be added")
|
||||
}
|
||||
ok = m.AddDef(s, s, "")
|
||||
if ok {
|
||||
t.Errorf("empty assertion value should not be added")
|
||||
}
|
||||
}
|
||||
|
||||
func TestModelToTest(t *testing.T) {
|
||||
testModelToText(t, "r.sub == p.sub && r.obj == p.obj && r_func(r.act, p.act) && testr_func(r.act, p.act)", "r_sub == p_sub && r_obj == p_obj && r_func(r_act, p_act) && testr_func(r_act, p_act)")
|
||||
testModelToText(t, "r.sub == p.sub && r.obj == p.obj && p_func(r.act, p.act) && testp_func(r.act, p.act)", "r_sub == p_sub && r_obj == p_obj && p_func(r_act, p_act) && testp_func(r_act, p_act)")
|
||||
}
|
||||
|
||||
func testModelToText(t *testing.T, mData, mExpected string) {
|
||||
m := NewModel()
|
||||
data := map[string]string{
|
||||
"r": "sub, obj, act",
|
||||
"p": "sub, obj, act",
|
||||
"e": "some(where (p.eft == allow))",
|
||||
"m": mData,
|
||||
}
|
||||
expected := map[string]string{
|
||||
"r": "sub, obj, act",
|
||||
"p": "sub, obj, act",
|
||||
"e": constant.AllowOverrideEffect,
|
||||
"m": mExpected,
|
||||
}
|
||||
addData := func(ptype string) {
|
||||
m.AddDef(ptype, ptype, data[ptype])
|
||||
}
|
||||
for ptype := range data {
|
||||
addData(ptype)
|
||||
}
|
||||
newM := NewModel()
|
||||
print(m.ToText())
|
||||
_ = newM.LoadModelFromText(m.ToText())
|
||||
for ptype := range data {
|
||||
if newM[ptype][ptype].Value != expected[ptype] {
|
||||
t.Errorf("\"%s\" assertion value changed, current value: %s, it should be: %s", ptype, newM[ptype][ptype].Value, expected[ptype])
|
||||
}
|
||||
}
|
||||
}
|
||||
482
model/policy.go
Normal file
482
model/policy.go
Normal file
@@ -0,0 +1,482 @@
|
||||
// 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 model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casbin/v3/constant"
|
||||
"github.com/casbin/casbin/v3/rbac"
|
||||
"github.com/casbin/casbin/v3/util"
|
||||
)
|
||||
|
||||
type (
|
||||
PolicyOp int
|
||||
)
|
||||
|
||||
const (
|
||||
PolicyAdd PolicyOp = iota
|
||||
PolicyRemove
|
||||
)
|
||||
|
||||
const DefaultSep = ","
|
||||
|
||||
// BuildIncrementalRoleLinks provides incremental build the role inheritance relations.
|
||||
func (model Model) BuildIncrementalRoleLinks(rmMap map[string]rbac.RoleManager, op PolicyOp, sec string, ptype string, rules [][]string) error {
|
||||
if sec == "g" && rmMap[ptype] != nil {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return model[sec][ptype].buildIncrementalRoleLinks(rmMap[ptype], op, rules)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildRoleLinks initializes the roles in RBAC.
|
||||
func (model Model) BuildRoleLinks(rmMap map[string]rbac.RoleManager) error {
|
||||
model.PrintPolicy()
|
||||
for ptype, ast := range model["g"] {
|
||||
if rm := rmMap[ptype]; rm != nil {
|
||||
err := ast.buildRoleLinks(rm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildIncrementalConditionalRoleLinks provides incremental build the role inheritance relations.
|
||||
func (model Model) BuildIncrementalConditionalRoleLinks(condRmMap map[string]rbac.ConditionalRoleManager, op PolicyOp, sec string, ptype string, rules [][]string) error {
|
||||
if sec == "g" && condRmMap[ptype] != nil {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return model[sec][ptype].buildIncrementalConditionalRoleLinks(condRmMap[ptype], op, rules)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildConditionalRoleLinks initializes the roles in RBAC.
|
||||
func (model Model) BuildConditionalRoleLinks(condRmMap map[string]rbac.ConditionalRoleManager) error {
|
||||
model.PrintPolicy()
|
||||
for ptype, ast := range model["g"] {
|
||||
if condRm := condRmMap[ptype]; condRm != nil {
|
||||
err := ast.buildConditionalRoleLinks(condRm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintPolicy prints the policy to log.
|
||||
func (model Model) PrintPolicy() {
|
||||
// Logger has been removed - this is now a no-op
|
||||
}
|
||||
|
||||
// ClearPolicy clears all current policy.
|
||||
func (model Model) ClearPolicy() {
|
||||
for _, ast := range model["p"] {
|
||||
ast.Policy = nil
|
||||
ast.PolicyMap = map[string]int{}
|
||||
}
|
||||
|
||||
for _, ast := range model["g"] {
|
||||
ast.Policy = nil
|
||||
ast.PolicyMap = map[string]int{}
|
||||
}
|
||||
}
|
||||
|
||||
// GetPolicy gets all rules in a policy.
|
||||
func (model Model) GetPolicy(sec string, ptype string) ([][]string, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return model[sec][ptype].Policy, nil
|
||||
}
|
||||
|
||||
// GetFilteredPolicy gets rules based on field filters from a policy.
|
||||
func (model Model) GetFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) ([][]string, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := [][]string{}
|
||||
|
||||
for _, rule := range model[sec][ptype].Policy {
|
||||
matched := true
|
||||
for i, fieldValue := range fieldValues {
|
||||
if fieldValue != "" && rule[fieldIndex+i] != fieldValue {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
res = append(res, rule)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// HasPolicyEx determines whether a model has the specified policy rule with error.
|
||||
func (model Model) HasPolicyEx(sec string, ptype string, rule []string) (bool, error) {
|
||||
assertion, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch sec {
|
||||
case "p":
|
||||
if len(rule) != len(assertion.Tokens) {
|
||||
return false, fmt.Errorf(
|
||||
"invalid policy rule size: expected %d, got %d, rule: %v",
|
||||
len(model["p"][ptype].Tokens),
|
||||
len(rule),
|
||||
rule)
|
||||
}
|
||||
case "g":
|
||||
if len(rule) < len(assertion.Tokens) {
|
||||
return false, fmt.Errorf(
|
||||
"invalid policy rule size: expected %d, got %d, rule: %v",
|
||||
len(model["g"][ptype].Tokens),
|
||||
len(rule),
|
||||
rule)
|
||||
}
|
||||
}
|
||||
return model.HasPolicy(sec, ptype, rule)
|
||||
}
|
||||
|
||||
// HasPolicy determines whether a model has the specified policy rule.
|
||||
func (model Model) HasPolicy(sec string, ptype string, rule []string) (bool, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
_, ok := model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)]
|
||||
return ok, nil
|
||||
}
|
||||
|
||||
// HasPolicies determines whether a model has any of the specified policies. If one is found we return true.
|
||||
func (model Model) HasPolicies(sec string, ptype string, rules [][]string) (bool, error) {
|
||||
for i := 0; i < len(rules); i++ {
|
||||
ok, err := model.HasPolicy(sec, ptype, rules[i])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ok {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// AddPolicy adds a policy rule to the model.
|
||||
func (model Model) AddPolicy(sec string, ptype string, rule []string) error {
|
||||
assertion, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assertion.Policy = append(assertion.Policy, rule)
|
||||
assertion.PolicyMap[strings.Join(rule, DefaultSep)] = len(model[sec][ptype].Policy) - 1
|
||||
|
||||
hasPriority := false
|
||||
if _, ok := assertion.FieldIndexMap[constant.PriorityIndex]; ok {
|
||||
hasPriority = true
|
||||
}
|
||||
if sec == "p" && hasPriority {
|
||||
if idxInsert, err := strconv.Atoi(rule[assertion.FieldIndexMap[constant.PriorityIndex]]); err == nil {
|
||||
i := len(assertion.Policy) - 1
|
||||
for ; i > 0; i-- {
|
||||
idx, err := strconv.Atoi(assertion.Policy[i-1][assertion.FieldIndexMap[constant.PriorityIndex]])
|
||||
if err != nil || idx <= idxInsert {
|
||||
break
|
||||
}
|
||||
assertion.Policy[i] = assertion.Policy[i-1]
|
||||
assertion.PolicyMap[strings.Join(assertion.Policy[i-1], DefaultSep)]++
|
||||
}
|
||||
assertion.Policy[i] = rule
|
||||
assertion.PolicyMap[strings.Join(rule, DefaultSep)] = i
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPolicies adds policy rules to the model.
|
||||
func (model Model) AddPolicies(sec string, ptype string, rules [][]string) error {
|
||||
_, err := model.AddPoliciesWithAffected(sec, ptype, rules)
|
||||
return err
|
||||
}
|
||||
|
||||
// AddPoliciesWithAffected adds policy rules to the model, and returns affected rules.
|
||||
func (model Model) AddPoliciesWithAffected(sec string, ptype string, rules [][]string) ([][]string, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var affected [][]string
|
||||
for _, rule := range rules {
|
||||
hashKey := strings.Join(rule, DefaultSep)
|
||||
_, ok := model[sec][ptype].PolicyMap[hashKey]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
affected = append(affected, rule)
|
||||
err = model.AddPolicy(sec, ptype, rule)
|
||||
if err != nil {
|
||||
return affected, err
|
||||
}
|
||||
}
|
||||
return affected, err
|
||||
}
|
||||
|
||||
// RemovePolicy removes a policy rule from the model.
|
||||
func (model Model) RemovePolicy(sec string, ptype string, rule []string) (bool, error) {
|
||||
ast, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
key := strings.Join(rule, DefaultSep)
|
||||
index, ok := ast.PolicyMap[key]
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// The order of Policy is significant: it carries the priority semantics of
|
||||
// the "priority(p_eft)" effector, both for explicit priority models and for
|
||||
// implicit ones where the priority is the policy order itself. So the rule
|
||||
// has to be shifted out instead of being swapped with the last one.
|
||||
ast.Policy = append(ast.Policy[:index], ast.Policy[index+1:]...)
|
||||
delete(ast.PolicyMap, key)
|
||||
for i := index; i < len(ast.Policy); i++ {
|
||||
ast.PolicyMap[strings.Join(ast.Policy[i], DefaultSep)] = i
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// UpdatePolicy updates a policy rule from the model.
|
||||
func (model Model) UpdatePolicy(sec string, ptype string, oldRule []string, newRule []string) (bool, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
oldPolicy := strings.Join(oldRule, DefaultSep)
|
||||
index, ok := model[sec][ptype].PolicyMap[oldPolicy]
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
model[sec][ptype].Policy[index] = newRule
|
||||
delete(model[sec][ptype].PolicyMap, oldPolicy)
|
||||
model[sec][ptype].PolicyMap[strings.Join(newRule, DefaultSep)] = index
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// UpdatePolicies updates a policy rule from the model.
|
||||
func (model Model) UpdatePolicies(sec string, ptype string, oldRules, newRules [][]string) (bool, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
rollbackFlag := false
|
||||
// index -> []{oldIndex, newIndex}
|
||||
modifiedRuleIndex := make(map[int][]int)
|
||||
// rollback
|
||||
defer func() {
|
||||
if rollbackFlag {
|
||||
for index, oldNewIndex := range modifiedRuleIndex {
|
||||
model[sec][ptype].Policy[index] = oldRules[oldNewIndex[0]]
|
||||
oldPolicy := strings.Join(oldRules[oldNewIndex[0]], DefaultSep)
|
||||
newPolicy := strings.Join(newRules[oldNewIndex[1]], DefaultSep)
|
||||
delete(model[sec][ptype].PolicyMap, newPolicy)
|
||||
model[sec][ptype].PolicyMap[oldPolicy] = index
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
newIndex := 0
|
||||
for oldIndex, oldRule := range oldRules {
|
||||
oldPolicy := strings.Join(oldRule, DefaultSep)
|
||||
index, ok := model[sec][ptype].PolicyMap[oldPolicy]
|
||||
if !ok {
|
||||
rollbackFlag = true
|
||||
return false, nil
|
||||
}
|
||||
|
||||
model[sec][ptype].Policy[index] = newRules[newIndex]
|
||||
delete(model[sec][ptype].PolicyMap, oldPolicy)
|
||||
model[sec][ptype].PolicyMap[strings.Join(newRules[newIndex], DefaultSep)] = index
|
||||
modifiedRuleIndex[index] = []int{oldIndex, newIndex}
|
||||
newIndex++
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// RemovePolicies removes policy rules from the model.
|
||||
func (model Model) RemovePolicies(sec string, ptype string, rules [][]string) (bool, error) {
|
||||
affected, err := model.RemovePoliciesWithAffected(sec, ptype, rules)
|
||||
return len(affected) != 0, err
|
||||
}
|
||||
|
||||
// RemovePoliciesWithAffected removes policy rules from the model, and returns affected rules.
|
||||
func (model Model) RemovePoliciesWithAffected(sec string, ptype string, rules [][]string) ([][]string, error) {
|
||||
assertion, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var affected [][]string
|
||||
removeSet := make(map[string]struct{}, len(rules))
|
||||
|
||||
for _, rule := range rules {
|
||||
key := strings.Join(rule, DefaultSep)
|
||||
if _, ok := assertion.PolicyMap[key]; !ok {
|
||||
continue
|
||||
}
|
||||
if _, exists := removeSet[key]; exists {
|
||||
continue
|
||||
}
|
||||
affected = append(affected, rule)
|
||||
removeSet[key] = struct{}{}
|
||||
}
|
||||
|
||||
if len(removeSet) == 0 {
|
||||
return affected, nil
|
||||
}
|
||||
|
||||
compactPolicy := assertion.Policy[:0]
|
||||
compactPolicyMap := make(map[string]int, len(assertion.Policy)-len(removeSet))
|
||||
for _, policyRule := range assertion.Policy {
|
||||
key := strings.Join(policyRule, DefaultSep)
|
||||
if _, ok := removeSet[key]; ok {
|
||||
continue
|
||||
}
|
||||
compactPolicyMap[key] = len(compactPolicy)
|
||||
compactPolicy = append(compactPolicy, policyRule)
|
||||
}
|
||||
|
||||
assertion.Policy = compactPolicy
|
||||
assertion.PolicyMap = compactPolicyMap
|
||||
|
||||
return affected, nil
|
||||
}
|
||||
|
||||
// RemoveFilteredPolicy removes policy rules based on field filters from the model.
|
||||
func (model Model) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) (bool, [][]string, error) {
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
var tmp [][]string
|
||||
var effects [][]string
|
||||
res := false
|
||||
model[sec][ptype].PolicyMap = map[string]int{}
|
||||
|
||||
for _, rule := range model[sec][ptype].Policy {
|
||||
matched := true
|
||||
for i, fieldValue := range fieldValues {
|
||||
if fieldValue != "" && rule[fieldIndex+i] != fieldValue {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
effects = append(effects, rule)
|
||||
} else {
|
||||
tmp = append(tmp, rule)
|
||||
model[sec][ptype].PolicyMap[strings.Join(rule, DefaultSep)] = len(tmp) - 1
|
||||
}
|
||||
}
|
||||
|
||||
if len(tmp) != len(model[sec][ptype].Policy) {
|
||||
model[sec][ptype].Policy = tmp
|
||||
res = true
|
||||
}
|
||||
|
||||
return res, effects, nil
|
||||
}
|
||||
|
||||
// GetValuesForFieldInPolicy gets all values for a field for all rules in a policy, duplicated values are removed.
|
||||
func (model Model) GetValuesForFieldInPolicy(sec string, ptype string, fieldIndex int) ([]string, error) {
|
||||
values := []string{}
|
||||
|
||||
_, err := model.GetAssertion(sec, ptype)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, rule := range model[sec][ptype].Policy {
|
||||
values = append(values, rule[fieldIndex])
|
||||
}
|
||||
|
||||
util.ArrayRemoveDuplicates(&values)
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// GetValuesForFieldInPolicyAllTypes gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed.
|
||||
func (model Model) GetValuesForFieldInPolicyAllTypes(sec string, fieldIndex int) ([]string, error) {
|
||||
values := []string{}
|
||||
|
||||
for ptype := range model[sec] {
|
||||
v, err := model.GetValuesForFieldInPolicy(sec, ptype, fieldIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values = append(values, v...)
|
||||
}
|
||||
|
||||
util.ArrayRemoveDuplicates(&values)
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// GetValuesForFieldInPolicyAllTypesByName gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed.
|
||||
func (model Model) GetValuesForFieldInPolicyAllTypesByName(sec string, field string) ([]string, error) {
|
||||
values := []string{}
|
||||
|
||||
for ptype := range model[sec] {
|
||||
// GetFieldIndex will return (-1, err) if field is not found, ignore it
|
||||
index, err := model.GetFieldIndex(ptype, field)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
v, err := model.GetValuesForFieldInPolicy(sec, ptype, index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values = append(values, v...)
|
||||
}
|
||||
|
||||
util.ArrayRemoveDuplicates(&values)
|
||||
|
||||
return values, nil
|
||||
}
|
||||
Reference in New Issue
Block a user