| // Copyright 2017 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 casbin |
| |
| import ( |
| "errors" |
| "fmt" |
| "runtime/debug" |
| "strings" |
| "sync" |
| |
| "github.com/casbin/casbin/v3/detector" |
| "github.com/casbin/casbin/v3/effector" |
| "github.com/casbin/casbin/v3/log" |
| "github.com/casbin/casbin/v3/model" |
| "github.com/casbin/casbin/v3/persist" |
| fileadapter "github.com/casbin/casbin/v3/persist/file-adapter" |
| "github.com/casbin/casbin/v3/rbac" |
| defaultrolemanager "github.com/casbin/casbin/v3/rbac/default-role-manager" |
| "github.com/casbin/casbin/v3/util" |
| |
| "github.com/casbin/govaluate" |
| ) |
| |
| // Enforcer is the main interface for authorization enforcement and policy management. |
| type Enforcer struct { |
| modelPath string |
| model model.Model |
| fm model.FunctionMap |
| eft effector.Effector |
| |
| adapter persist.Adapter |
| watcher persist.Watcher |
| dispatcher persist.Dispatcher |
| rmMap map[string]rbac.RoleManager |
| condRmMap map[string]rbac.ConditionalRoleManager |
| matcherMap sync.Map |
| logger log.Logger |
| detectors []detector.Detector |
| |
| enabled bool |
| autoSave bool |
| autoBuildRoleLinks bool |
| autoNotifyWatcher bool |
| autoNotifyDispatcher bool |
| acceptJsonRequest bool |
| } |
| |
| // EnforceContext is used as the first element of the parameter "rvals" in method "enforce". |
| type EnforceContext struct { |
| RType string |
| PType string |
| EType string |
| MType string |
| } |
| |
| func (e EnforceContext) GetCacheKey() string { |
| return "EnforceContext{" + e.RType + "-" + e.PType + "-" + e.EType + "-" + e.MType + "}" |
| } |
| |
| // NewEnforcer creates an enforcer via file or DB. |
| // |
| // File: |
| // |
| // e := casbin.NewEnforcer("path/to/basic_model.conf", "path/to/basic_policy.csv") |
| // |
| // MySQL DB: |
| // |
| // a := mysqladapter.NewDBAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/") |
| // e := casbin.NewEnforcer("path/to/basic_model.conf", a) |
| func NewEnforcer(params ...interface{}) (*Enforcer, error) { |
| e := &Enforcer{} |
| |
| parsedParamLen := 0 |
| paramLen := len(params) |
| |
| switch paramLen - parsedParamLen { |
| case 2: |
| switch p0 := params[0].(type) { |
| case string: |
| switch p1 := params[1].(type) { |
| case string: |
| err := e.InitWithFile(p0, p1) |
| if err != nil { |
| return nil, err |
| } |
| default: |
| err := e.InitWithAdapter(p0, p1.(persist.Adapter)) |
| if err != nil { |
| return nil, err |
| } |
| } |
| default: |
| switch params[1].(type) { |
| case string: |
| return nil, errors.New("invalid parameters for enforcer") |
| default: |
| err := e.InitWithModelAndAdapter(p0.(model.Model), params[1].(persist.Adapter)) |
| if err != nil { |
| return nil, err |
| } |
| } |
| } |
| case 1: |
| switch p0 := params[0].(type) { |
| case string: |
| err := e.InitWithFile(p0, "") |
| if err != nil { |
| return nil, err |
| } |
| default: |
| err := e.InitWithModelAndAdapter(p0.(model.Model), nil) |
| if err != nil { |
| return nil, err |
| } |
| } |
| case 0: |
| return e, nil |
| default: |
| return nil, errors.New("invalid parameters for enforcer") |
| } |
| |
| return e, nil |
| } |
| |
| // InitWithFile initializes an enforcer with a model file and a policy file. |
| func (e *Enforcer) InitWithFile(modelPath string, policyPath string) error { |
| a := fileadapter.NewAdapter(policyPath) |
| return e.InitWithAdapter(modelPath, a) |
| } |
| |
| // InitWithAdapter initializes an enforcer with a database adapter. |
| func (e *Enforcer) InitWithAdapter(modelPath string, adapter persist.Adapter) error { |
| m, err := model.NewModelFromFile(modelPath) |
| if err != nil { |
| return err |
| } |
| |
| err = e.InitWithModelAndAdapter(m, adapter) |
| if err != nil { |
| return err |
| } |
| |
| e.modelPath = modelPath |
| return nil |
| } |
| |
| // InitWithModelAndAdapter initializes an enforcer with a model and a database adapter. |
| func (e *Enforcer) InitWithModelAndAdapter(m model.Model, adapter persist.Adapter) error { |
| e.adapter = adapter |
| |
| e.model = m |
| e.model.PrintModel() |
| e.fm = model.LoadFunctionMap() |
| |
| e.initialize() |
| |
| // Do not initialize the full policy when using a filtered adapter |
| fa, ok := e.adapter.(persist.FilteredAdapter) |
| if e.adapter != nil && (!ok || ok && !fa.IsFiltered()) { |
| err := e.LoadPolicy() |
| if err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| func (e *Enforcer) initialize() { |
| e.rmMap = map[string]rbac.RoleManager{} |
| e.condRmMap = map[string]rbac.ConditionalRoleManager{} |
| e.eft = effector.NewDefaultEffector() |
| e.watcher = nil |
| e.matcherMap = sync.Map{} |
| |
| e.enabled = true |
| e.autoSave = true |
| e.autoBuildRoleLinks = true |
| e.autoNotifyWatcher = true |
| e.autoNotifyDispatcher = true |
| e.initRmMap() |
| |
| // Initialize detectors with default detector if not already set |
| if e.detectors == nil { |
| e.detectors = []detector.Detector{detector.NewDefaultDetector()} |
| } |
| } |
| |
| // LoadModel reloads the model from the model CONF file. |
| // Because the policy is attached to a model, so the policy is invalidated and needs to be reloaded by calling LoadPolicy(). |
| func (e *Enforcer) LoadModel() error { |
| var err error |
| e.model, err = model.NewModelFromFile(e.modelPath) |
| if err != nil { |
| return err |
| } |
| |
| e.model.PrintModel() |
| e.fm = model.LoadFunctionMap() |
| |
| e.initialize() |
| |
| return nil |
| } |
| |
| // GetModel gets the current model. |
| func (e *Enforcer) GetModel() model.Model { |
| return e.model |
| } |
| |
| // SetModel sets the current model. |
| func (e *Enforcer) SetModel(m model.Model) { |
| e.model = m |
| e.fm = model.LoadFunctionMap() |
| |
| e.initialize() |
| } |
| |
| // GetAdapter gets the current adapter. |
| func (e *Enforcer) GetAdapter() persist.Adapter { |
| return e.adapter |
| } |
| |
| // SetAdapter sets the current adapter. |
| func (e *Enforcer) SetAdapter(adapter persist.Adapter) { |
| e.adapter = adapter |
| } |
| |
| // SetWatcher sets the current watcher. |
| func (e *Enforcer) SetWatcher(watcher persist.Watcher) error { |
| e.watcher = watcher |
| if _, ok := e.watcher.(persist.WatcherEx); ok { |
| // The callback of WatcherEx has no generic implementation. |
| return nil |
| } else { |
| // In case the Watcher wants to use a customized callback function, call `SetUpdateCallback` after `SetWatcher`. |
| return watcher.SetUpdateCallback(func(string) { _ = e.LoadPolicy() }) |
| } |
| } |
| |
| // GetRoleManager gets the current role manager. |
| func (e *Enforcer) GetRoleManager() rbac.RoleManager { |
| if e.rmMap != nil && e.rmMap["g"] != nil { |
| return e.rmMap["g"] |
| } else if e.condRmMap != nil && e.condRmMap["g"] != nil { |
| return e.condRmMap["g"] |
| } else { |
| return nil |
| } |
| } |
| |
| // GetNamedRoleManager gets the role manager for the named policy. |
| func (e *Enforcer) GetNamedRoleManager(ptype string) rbac.RoleManager { |
| if e.rmMap != nil && e.rmMap[ptype] != nil { |
| return e.rmMap[ptype] |
| } else if e.condRmMap != nil && e.condRmMap[ptype] != nil { |
| return e.condRmMap[ptype] |
| } else { |
| return nil |
| } |
| } |
| |
| // SetRoleManager sets the current role manager. |
| func (e *Enforcer) SetRoleManager(rm rbac.RoleManager) { |
| e.invalidateMatcherMap() |
| e.rmMap["g"] = rm |
| } |
| |
| // SetNamedRoleManager sets the role manager for the named policy. |
| func (e *Enforcer) SetNamedRoleManager(ptype string, rm rbac.RoleManager) { |
| e.invalidateMatcherMap() |
| e.rmMap[ptype] = rm |
| } |
| |
| // SetEffector sets the current effector. |
| func (e *Enforcer) SetEffector(eft effector.Effector) { |
| e.eft = eft |
| } |
| |
| // SetLogger sets the logger for the enforcer. |
| func (e *Enforcer) SetLogger(logger log.Logger) { |
| e.logger = logger |
| } |
| |
| // SetDetector sets a single detector for the enforcer. |
| func (e *Enforcer) SetDetector(d detector.Detector) { |
| e.detectors = []detector.Detector{d} |
| } |
| |
| // SetDetectors sets multiple detectors for the enforcer. |
| func (e *Enforcer) SetDetectors(detectors []detector.Detector) { |
| e.detectors = detectors |
| } |
| |
| // RunDetections runs all detectors on all role managers. |
| // Returns the first error encountered, or nil if all checks pass. |
| // Silently skips role managers that don't support the required iteration methods. |
| func (e *Enforcer) RunDetections() error { |
| if e.detectors == nil || len(e.detectors) == 0 { |
| return nil |
| } |
| |
| // Run detectors on all role managers |
| for _, rm := range e.rmMap { |
| for _, d := range e.detectors { |
| err := d.Check(rm) |
| // Skip if the role manager doesn't support the required iteration or is not initialized |
| if err != nil && (strings.Contains(err.Error(), "does not support Range iteration") || |
| strings.Contains(err.Error(), "not properly initialized")) { |
| continue |
| } |
| if err != nil { |
| return err |
| } |
| } |
| } |
| |
| // Run detectors on all conditional role managers |
| for _, crm := range e.condRmMap { |
| for _, d := range e.detectors { |
| err := d.Check(crm) |
| // Skip if the role manager doesn't support the required iteration or is not initialized |
| if err != nil && (strings.Contains(err.Error(), "does not support Range iteration") || |
| strings.Contains(err.Error(), "not properly initialized")) { |
| continue |
| } |
| if err != nil { |
| return err |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| // ClearPolicy clears all policy. |
| func (e *Enforcer) ClearPolicy() { |
| e.invalidateMatcherMap() |
| |
| if e.dispatcher != nil && e.autoNotifyDispatcher { |
| _ = e.dispatcher.ClearPolicy() |
| return |
| } |
| e.model.ClearPolicy() |
| } |
| |
| // LoadPolicy reloads the policy from file/database. |
| func (e *Enforcer) LoadPolicy() error { |
| logEntry := e.onLogBeforeEventInLoadPolicy() |
| |
| newModel, err := e.loadPolicyFromAdapter(e.model) |
| if err != nil { |
| e.onLogAfterEventWithError(logEntry, err) |
| return err |
| } |
| err = e.applyModifiedModel(newModel) |
| if err != nil { |
| e.onLogAfterEventWithError(logEntry, err) |
| return err |
| } |
| |
| e.onLogAfterEventInLoadPolicy(logEntry, newModel) |
| |
| // Run detectors after all policy rules are loaded |
| err = e.RunDetections() |
| if err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func (e *Enforcer) loadPolicyFromAdapter(baseModel model.Model) (model.Model, error) { |
| newModel := baseModel.Copy() |
| newModel.ClearPolicy() |
| |
| if err := e.adapter.LoadPolicy(newModel); err != nil && err.Error() != "invalid file path, file path cannot be empty" { |
| return nil, err |
| } |
| |
| if err := newModel.SortPoliciesBySubjectHierarchy(); err != nil { |
| return nil, err |
| } |
| |
| if err := newModel.SortPoliciesByPriority(); err != nil { |
| return nil, err |
| } |
| |
| return newModel, nil |
| } |
| |
| func (e *Enforcer) applyModifiedModel(newModel model.Model) error { |
| var err error |
| needToRebuild := false |
| defer func() { |
| if err != nil { |
| if e.autoBuildRoleLinks && needToRebuild { |
| _ = e.BuildRoleLinks() |
| } |
| } |
| }() |
| |
| if e.autoBuildRoleLinks { |
| needToRebuild = true |
| |
| if err := e.rebuildRoleLinks(newModel); err != nil { |
| return err |
| } |
| |
| if err := e.rebuildConditionalRoleLinks(newModel); err != nil { |
| return err |
| } |
| } |
| |
| e.model = newModel |
| e.invalidateMatcherMap() |
| return nil |
| } |
| |
| func (e *Enforcer) rebuildRoleLinks(newModel model.Model) error { |
| if len(e.rmMap) != 0 { |
| for _, rm := range e.rmMap { |
| err := rm.Clear() |
| if err != nil { |
| return err |
| } |
| } |
| |
| err := newModel.BuildRoleLinks(e.rmMap) |
| if err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| func (e *Enforcer) rebuildConditionalRoleLinks(newModel model.Model) error { |
| if len(e.condRmMap) != 0 { |
| for _, crm := range e.condRmMap { |
| err := crm.Clear() |
| if err != nil { |
| return err |
| } |
| } |
| |
| err := newModel.BuildConditionalRoleLinks(e.condRmMap) |
| if err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| func (e *Enforcer) loadFilteredPolicy(filter interface{}) error { |
| e.invalidateMatcherMap() |
| |
| var filteredAdapter persist.FilteredAdapter |
| |
| // Attempt to cast the Adapter as a FilteredAdapter |
| switch adapter := e.adapter.(type) { |
| case persist.FilteredAdapter: |
| filteredAdapter = adapter |
| default: |
| return errors.New("filtered policies are not supported by this adapter") |
| } |
| if err := filteredAdapter.LoadFilteredPolicy(e.model, filter); err != nil && err.Error() != "invalid file path, file path cannot be empty" { |
| return err |
| } |
| |
| if err := e.model.SortPoliciesBySubjectHierarchy(); err != nil { |
| return err |
| } |
| |
| if err := e.model.SortPoliciesByPriority(); err != nil { |
| return err |
| } |
| |
| e.initRmMap() |
| e.model.PrintPolicy() |
| if e.autoBuildRoleLinks { |
| err := e.BuildRoleLinks() |
| if err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // LoadFilteredPolicy reloads a filtered policy from file/database. |
| func (e *Enforcer) LoadFilteredPolicy(filter interface{}) error { |
| e.model.ClearPolicy() |
| |
| return e.loadFilteredPolicy(filter) |
| } |
| |
| // LoadIncrementalFilteredPolicy append a filtered policy from file/database. |
| func (e *Enforcer) LoadIncrementalFilteredPolicy(filter interface{}) error { |
| return e.loadFilteredPolicy(filter) |
| } |
| |
| // IsFiltered returns true if the loaded policy has been filtered. |
| func (e *Enforcer) IsFiltered() bool { |
| filteredAdapter, ok := e.adapter.(persist.FilteredAdapter) |
| if !ok { |
| return false |
| } |
| return filteredAdapter.IsFiltered() |
| } |
| |
| // SavePolicy saves the current policy (usually after changed with Casbin API) back to file/database. |
| func (e *Enforcer) SavePolicy() error { |
| logEntry := e.onLogBeforeEventInSavePolicy() |
| |
| if e.IsFiltered() { |
| err := errors.New("cannot save a filtered policy") |
| e.onLogAfterEventWithError(logEntry, err) |
| return err |
| } |
| if err := e.adapter.SavePolicy(e.model); err != nil { |
| e.onLogAfterEventWithError(logEntry, err) |
| return err |
| } |
| |
| e.onLogAfterEventInSavePolicy(logEntry) |
| |
| if e.watcher != nil { |
| var err error |
| if watcher, ok := e.watcher.(persist.WatcherEx); ok { |
| err = watcher.UpdateForSavePolicy(e.model) |
| } else { |
| err = e.watcher.Update() |
| } |
| return err |
| } |
| return nil |
| } |
| |
| // getDomainTokens extracts domain token names from request and policy definitions. |
| // Returns empty strings if tokens cannot be found. |
| func (e *Enforcer) getDomainTokens() (rDomainToken, pDomainToken string) { |
| if rAssertion, ok := e.model["r"]["r"]; ok && len(rAssertion.Tokens) > 1 { |
| rDomainToken = rAssertion.Tokens[1] |
| } |
| if pAssertion, ok := e.model["p"]["p"]; ok && len(pAssertion.Tokens) > 1 { |
| pDomainToken = pAssertion.Tokens[1] |
| } |
| return rDomainToken, pDomainToken |
| } |
| |
| // registerDomainMatchingFunc registers domain matching function if the matcher uses keyMatch for domains. |
| func (e *Enforcer) registerDomainMatchingFunc(ptype string) { |
| // Dynamically detect the domain token name from the model definition. |
| // In RBAC with domains, the domain is typically the second parameter (index 1) |
| // in both request and policy definitions (e.g., r = sub, dom, obj, act). |
| // We extract the actual token names to support arbitrary domain parameter names. |
| rDomainToken, pDomainToken := e.getDomainTokens() |
| if rDomainToken == "" || pDomainToken == "" { |
| return |
| } |
| |
| matchFun := fmt.Sprintf("keyMatch(%s, %s)", rDomainToken, pDomainToken) |
| if strings.Contains(e.model["m"]["m"].Value, matchFun) { |
| e.AddNamedDomainMatchingFunc(ptype, "g", util.KeyMatch) |
| } |
| } |
| |
| func (e *Enforcer) initRmMap() { |
| for ptype, assertion := range e.model["g"] { |
| if rm, ok := e.rmMap[ptype]; ok { |
| _ = rm.Clear() |
| continue |
| } |
| if len(assertion.Tokens) <= 2 && len(assertion.ParamsTokens) == 0 { |
| assertion.RM = defaultrolemanager.NewRoleManagerImpl(10) |
| e.rmMap[ptype] = assertion.RM |
| } |
| if len(assertion.Tokens) <= 2 && len(assertion.ParamsTokens) != 0 { |
| assertion.CondRM = defaultrolemanager.NewConditionalRoleManager(10) |
| e.condRmMap[ptype] = assertion.CondRM |
| } |
| if len(assertion.Tokens) > 2 { |
| if len(assertion.ParamsTokens) == 0 { |
| assertion.RM = defaultrolemanager.NewRoleManager(10) |
| e.rmMap[ptype] = assertion.RM |
| } else { |
| assertion.CondRM = defaultrolemanager.NewConditionalDomainManager(10) |
| e.condRmMap[ptype] = assertion.CondRM |
| } |
| e.registerDomainMatchingFunc(ptype) |
| } |
| } |
| } |
| |
| // EnableEnforce changes the enforcing state of Casbin, when Casbin is disabled, all access will be allowed by the Enforce() function. |
| func (e *Enforcer) EnableEnforce(enable bool) { |
| e.enabled = enable |
| } |
| |
| // EnableAutoNotifyWatcher controls whether to save a policy rule automatically notify the Watcher when it is added or removed. |
| func (e *Enforcer) EnableAutoNotifyWatcher(enable bool) { |
| e.autoNotifyWatcher = enable |
| } |
| |
| // EnableAutoNotifyDispatcher controls whether to save a policy rule automatically notify the Dispatcher when it is added or removed. |
| func (e *Enforcer) EnableAutoNotifyDispatcher(enable bool) { |
| e.autoNotifyDispatcher = enable |
| } |
| |
| // EnableAutoSave controls whether to save a policy rule automatically to the adapter when it is added or removed. |
| func (e *Enforcer) EnableAutoSave(autoSave bool) { |
| e.autoSave = autoSave |
| } |
| |
| // EnableAutoBuildRoleLinks controls whether to rebuild the role inheritance relations when a role is added or deleted. |
| func (e *Enforcer) EnableAutoBuildRoleLinks(autoBuildRoleLinks bool) { |
| e.autoBuildRoleLinks = autoBuildRoleLinks |
| } |
| |
| // EnableAcceptJsonRequest controls whether to accept json as a request parameter. |
| func (e *Enforcer) EnableAcceptJsonRequest(acceptJsonRequest bool) { |
| e.acceptJsonRequest = acceptJsonRequest |
| } |
| |
| // BuildRoleLinks manually rebuild the role inheritance relations. |
| func (e *Enforcer) BuildRoleLinks() error { |
| e.invalidateMatcherMap() |
| if e.rmMap == nil { |
| return errors.New("rmMap is nil") |
| } |
| for _, rm := range e.rmMap { |
| err := rm.Clear() |
| if err != nil { |
| return err |
| } |
| } |
| |
| return e.model.BuildRoleLinks(e.rmMap) |
| } |
| |
| // BuildIncrementalRoleLinks provides incremental build the role inheritance relations. |
| func (e *Enforcer) BuildIncrementalRoleLinks(op model.PolicyOp, ptype string, rules [][]string) error { |
| e.invalidateMatcherMap() |
| return e.model.BuildIncrementalRoleLinks(e.rmMap, op, "g", ptype, rules) |
| } |
| |
| // BuildIncrementalConditionalRoleLinks provides incremental build the role inheritance relations with conditions. |
| func (e *Enforcer) BuildIncrementalConditionalRoleLinks(op model.PolicyOp, ptype string, rules [][]string) error { |
| e.invalidateMatcherMap() |
| return e.model.BuildIncrementalConditionalRoleLinks(e.condRmMap, op, "g", ptype, rules) |
| } |
| |
| // NewEnforceContext Create a default structure based on the suffix. |
| func NewEnforceContext(suffix string) EnforceContext { |
| return EnforceContext{ |
| RType: "r" + suffix, |
| PType: "p" + suffix, |
| EType: "e" + suffix, |
| MType: "m" + suffix, |
| } |
| } |
| |
| func (e *Enforcer) invalidateMatcherMap() { |
| e.matcherMap = sync.Map{} |
| } |
| |
| // enforce use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "". |
| func (e *Enforcer) enforce(matcher string, explains *[]string, rvals ...interface{}) (ok bool, err error) { //nolint:funlen,cyclop,gocyclo // TODO: reduce function complexity |
| logEntry := e.onLogBeforeEventInEnforce(rvals) |
| |
| defer func() { |
| if r := recover(); r != nil { |
| err = fmt.Errorf("panic: %v\n%s", r, debug.Stack()) |
| if e.logger != nil && logEntry != nil { |
| logEntry.Error = err |
| } |
| } |
| e.onLogAfterEventInEnforce(logEntry, ok) |
| }() |
| |
| if !e.enabled { |
| return true, nil |
| } |
| |
| functions := e.fm.GetFunctions() |
| if _, ok := e.model["g"]; ok { |
| for key, ast := range e.model["g"] { |
| // g must be a normal role definition (ast.RM != nil) |
| // or a conditional role definition (ast.CondRM != nil) |
| // ast.RM and ast.CondRM shouldn't be nil at the same time |
| if ast.RM != nil { |
| functions[key] = util.GenerateGFunction(ast.RM) |
| } |
| if ast.CondRM != nil { |
| functions[key] = util.GenerateConditionalGFunction(ast.CondRM) |
| } |
| } |
| } |
| |
| var ( |
| rType = "r" |
| pType = "p" |
| eType = "e" |
| mType = "m" |
| ) |
| if len(rvals) != 0 { |
| switch rvals[0].(type) { |
| case EnforceContext: |
| enforceContext := rvals[0].(EnforceContext) |
| rType = enforceContext.RType |
| pType = enforceContext.PType |
| eType = enforceContext.EType |
| mType = enforceContext.MType |
| rvals = rvals[1:] |
| default: |
| break |
| } |
| } |
| |
| var expString string |
| if matcher == "" { |
| expString = e.model["m"][mType].Value |
| } else { |
| // For custom matchers provided at runtime, escape backslashes in string literals |
| expString = util.EscapeStringLiterals(util.RemoveComments(util.EscapeAssertion(matcher))) |
| } |
| |
| rTokens := make(map[string]int, len(e.model["r"][rType].Tokens)) |
| for i, token := range e.model["r"][rType].Tokens { |
| rTokens[token] = i |
| } |
| pTokens := make(map[string]int, len(e.model["p"][pType].Tokens)) |
| for i, token := range e.model["p"][pType].Tokens { |
| pTokens[token] = i |
| } |
| |
| if e.acceptJsonRequest { |
| // try to parse all request values from json to map[string]interface{} |
| for i, rval := range rvals { |
| switch rval := rval.(type) { |
| case string: |
| // Only attempt JSON parsing for strings that look like JSON objects or arrays |
| if len(rval) > 0 && (rval[0] == '{' || rval[0] == '[') { |
| var mapValue map[string]interface{} |
| mapValue, err = util.JsonToMap(rval) |
| if err != nil { |
| // Return a clear error when JSON-like string fails to parse |
| return false, fmt.Errorf("failed to parse JSON parameter at index %d: %w", i, err) |
| } |
| rvals[i] = mapValue |
| } |
| } |
| } |
| } |
| |
| parameters := enforceParameters{ |
| rTokens: rTokens, |
| rVals: rvals, |
| |
| pTokens: pTokens, |
| } |
| |
| hasEval := util.HasEval(expString) |
| if hasEval { |
| functions["eval"] = generateEvalFunction(functions, ¶meters) |
| } |
| var expression *govaluate.EvaluableExpression |
| expression, err = e.getAndStoreMatcherExpression(hasEval, expString, functions) |
| if err != nil { |
| return false, err |
| } |
| |
| if len(e.model["r"][rType].Tokens) != len(rvals) { |
| return false, fmt.Errorf( |
| "invalid request size: expected %d, got %d, rvals: %v", |
| len(e.model["r"][rType].Tokens), |
| len(rvals), |
| rvals) |
| } |
| |
| var policyEffects []effector.Effect |
| var matcherResults []float64 |
| |
| var effect effector.Effect |
| var explainIndex int |
| |
| if policyLen := len(e.model["p"][pType].Policy); policyLen != 0 && strings.Contains(expString, pType+"_") { //nolint:nestif // TODO: reduce function complexity |
| policyEffects = make([]effector.Effect, policyLen) |
| matcherResults = make([]float64, policyLen) |
| |
| for policyIndex, pvals := range e.model["p"][pType].Policy { |
| // log.LogPrint("Policy Rule: ", pvals) |
| if len(e.model["p"][pType].Tokens) != len(pvals) { |
| return false, fmt.Errorf( |
| "invalid policy size: expected %d, got %d, pvals: %v", |
| len(e.model["p"][pType].Tokens), |
| len(pvals), |
| pvals) |
| } |
| |
| parameters.pVals = pvals |
| |
| result, err := expression.Eval(parameters) |
| // log.LogPrint("Result: ", result) |
| |
| if err != nil { |
| return false, err |
| } |
| |
| // set to no-match at first |
| matcherResults[policyIndex] = 0 |
| switch result := result.(type) { |
| case bool: |
| if result { |
| matcherResults[policyIndex] = 1 |
| } |
| case float64: |
| if result != 0 { |
| matcherResults[policyIndex] = 1 |
| } |
| default: |
| return false, errors.New("matcher result should be bool, int or float") |
| } |
| |
| if j, ok := parameters.pTokens[pType+"_eft"]; ok { |
| eft := parameters.pVals[j] |
| if eft == "allow" { |
| policyEffects[policyIndex] = effector.Allow |
| } else if eft == "deny" { |
| policyEffects[policyIndex] = effector.Deny |
| } else { |
| policyEffects[policyIndex] = effector.Indeterminate |
| } |
| } else { |
| policyEffects[policyIndex] = effector.Allow |
| } |
| |
| // if e.model["e"]["e"].Value == "priority(p_eft) || deny" { |
| // break |
| // } |
| |
| effect, explainIndex, err = e.eft.MergeEffects(e.model["e"][eType].Value, policyEffects, matcherResults, policyIndex, policyLen) |
| if err != nil { |
| return false, err |
| } |
| if effect != effector.Indeterminate { |
| break |
| } |
| } |
| } else { |
| if hasEval && len(e.model["p"][pType].Policy) == 0 { |
| return false, errors.New("please make sure rule exists in policy when using eval() in matcher") |
| } |
| |
| policyEffects = make([]effector.Effect, 1) |
| matcherResults = make([]float64, 1) |
| matcherResults[0] = 1 |
| |
| parameters.pVals = make([]string, len(parameters.pTokens)) |
| |
| result, err := expression.Eval(parameters) |
| |
| if err != nil { |
| return false, err |
| } |
| |
| if result.(bool) { |
| policyEffects[0] = effector.Allow |
| } else { |
| policyEffects[0] = effector.Indeterminate |
| } |
| |
| effect, explainIndex, err = e.eft.MergeEffects(e.model["e"][eType].Value, policyEffects, matcherResults, 0, 1) |
| if err != nil { |
| return false, err |
| } |
| } |
| |
| if explains != nil { |
| if explainIndex != -1 && len(e.model["p"][pType].Policy) > explainIndex { |
| *explains = e.model["p"][pType].Policy[explainIndex] |
| } |
| } |
| |
| // effect -> result |
| result := false |
| if effect == effector.Allow { |
| result = true |
| } |
| |
| return result, nil |
| } |
| |
| func (e *Enforcer) getAndStoreMatcherExpression(hasEval bool, expString string, functions map[string]govaluate.ExpressionFunction) (*govaluate.EvaluableExpression, error) { |
| var expression *govaluate.EvaluableExpression |
| var err error |
| var cachedExpression, isPresent = e.matcherMap.Load(expString) |
| |
| if !hasEval && isPresent { |
| expression = cachedExpression.(*govaluate.EvaluableExpression) |
| } else { |
| expression, err = govaluate.NewEvaluableExpressionWithFunctions(expString, functions) |
| if err != nil { |
| return nil, err |
| } |
| e.matcherMap.Store(expString, expression) |
| } |
| return expression, nil |
| } |
| |
| // Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act). |
| func (e *Enforcer) Enforce(rvals ...interface{}) (bool, error) { |
| return e.enforce("", nil, rvals...) |
| } |
| |
| // EnforceWithMatcher use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "". |
| func (e *Enforcer) EnforceWithMatcher(matcher string, rvals ...interface{}) (bool, error) { |
| return e.enforce(matcher, nil, rvals...) |
| } |
| |
| // EnforceEx explain enforcement by informing matched rules. |
| func (e *Enforcer) EnforceEx(rvals ...interface{}) (bool, []string, error) { |
| explain := []string{} |
| result, err := e.enforce("", &explain, rvals...) |
| return result, explain, err |
| } |
| |
| // EnforceExWithMatcher use a custom matcher and explain enforcement by informing matched rules. |
| func (e *Enforcer) EnforceExWithMatcher(matcher string, rvals ...interface{}) (bool, []string, error) { |
| explain := []string{} |
| result, err := e.enforce(matcher, &explain, rvals...) |
| return result, explain, err |
| } |
| |
| // BatchEnforce enforce in batches. |
| func (e *Enforcer) BatchEnforce(requests [][]interface{}) ([]bool, error) { |
| var results []bool |
| for _, request := range requests { |
| result, err := e.enforce("", nil, request...) |
| if err != nil { |
| return results, err |
| } |
| results = append(results, result) |
| } |
| return results, nil |
| } |
| |
| // BatchEnforceWithMatcher enforce with matcher in batches. |
| func (e *Enforcer) BatchEnforceWithMatcher(matcher string, requests [][]interface{}) ([]bool, error) { |
| var results []bool |
| for _, request := range requests { |
| result, err := e.enforce(matcher, nil, request...) |
| if err != nil { |
| return results, err |
| } |
| results = append(results, result) |
| } |
| return results, nil |
| } |
| |
| // AddNamedMatchingFunc add MatchingFunc by ptype RoleManager. |
| func (e *Enforcer) AddNamedMatchingFunc(ptype, name string, fn rbac.MatchingFunc) bool { |
| if rm, ok := e.rmMap[ptype]; ok { |
| rm.AddMatchingFunc(name, fn) |
| return true |
| } |
| return false |
| } |
| |
| // AddNamedDomainMatchingFunc add MatchingFunc by ptype to RoleManager. |
| func (e *Enforcer) AddNamedDomainMatchingFunc(ptype, name string, fn rbac.MatchingFunc) bool { |
| if rm, ok := e.rmMap[ptype]; ok { |
| rm.AddDomainMatchingFunc(name, fn) |
| return true |
| } |
| if condRm, ok := e.condRmMap[ptype]; ok { |
| condRm.AddDomainMatchingFunc(name, fn) |
| return true |
| } |
| return false |
| } |
| |
| // AddNamedLinkConditionFunc Add condition function fn for Link userName->roleName, |
| // when fn returns true, Link is valid, otherwise invalid. |
| func (e *Enforcer) AddNamedLinkConditionFunc(ptype, user, role string, fn rbac.LinkConditionFunc) bool { |
| if rm, ok := e.condRmMap[ptype]; ok { |
| rm.AddLinkConditionFunc(user, role, fn) |
| return true |
| } |
| return false |
| } |
| |
| // AddNamedDomainLinkConditionFunc Add condition function fn for Link userName-> {roleName, domain}, |
| // when fn returns true, Link is valid, otherwise invalid. |
| func (e *Enforcer) AddNamedDomainLinkConditionFunc(ptype, user, role string, domain string, fn rbac.LinkConditionFunc) bool { |
| if rm, ok := e.condRmMap[ptype]; ok { |
| rm.AddDomainLinkConditionFunc(user, role, domain, fn) |
| return true |
| } |
| return false |
| } |
| |
| // SetNamedLinkConditionFuncParams Sets the parameters of the condition function fn for Link userName->roleName. |
| func (e *Enforcer) SetNamedLinkConditionFuncParams(ptype, user, role string, params ...string) bool { |
| if rm, ok := e.condRmMap[ptype]; ok { |
| rm.SetLinkConditionFuncParams(user, role, params...) |
| return true |
| } |
| return false |
| } |
| |
| // SetNamedDomainLinkConditionFuncParams Sets the parameters of the condition function fn |
| // for Link userName->{roleName, domain}. |
| func (e *Enforcer) SetNamedDomainLinkConditionFuncParams(ptype, user, role, domain string, params ...string) bool { |
| if rm, ok := e.condRmMap[ptype]; ok { |
| rm.SetDomainLinkConditionFuncParams(user, role, domain, params...) |
| return true |
| } |
| return false |
| } |
| |
| // assumes bounds have already been checked. |
| type enforceParameters struct { |
| rTokens map[string]int |
| rVals []interface{} |
| |
| pTokens map[string]int |
| pVals []string |
| } |
| |
| // implements govaluate.Parameters. |
| func (p enforceParameters) Get(name string) (interface{}, error) { |
| if name == "" { |
| return nil, nil |
| } |
| |
| switch name[0] { |
| case 'p': |
| i, ok := p.pTokens[name] |
| if !ok { |
| return nil, errors.New("No parameter '" + name + "' found.") |
| } |
| return p.pVals[i], nil |
| case 'r': |
| i, ok := p.rTokens[name] |
| if !ok { |
| return nil, errors.New("No parameter '" + name + "' found.") |
| } |
| return p.rVals[i], nil |
| default: |
| return nil, errors.New("No parameter '" + name + "' found.") |
| } |
| } |
| |
| func generateEvalFunction(functions map[string]govaluate.ExpressionFunction, parameters *enforceParameters) govaluate.ExpressionFunction { |
| return func(args ...interface{}) (interface{}, error) { |
| if len(args) != 1 { |
| return nil, fmt.Errorf("function eval(subrule string) expected %d arguments, but got %d", 1, len(args)) |
| } |
| |
| expression, ok := args[0].(string) |
| if !ok { |
| return nil, errors.New("argument of eval(subrule string) must be a string") |
| } |
| expression = util.EscapeAssertion(expression) |
| expr, err := govaluate.NewEvaluableExpressionWithFunctions(expression, functions) |
| if err != nil { |
| return nil, fmt.Errorf("error while parsing eval parameter: %s, %s", expression, err.Error()) |
| } |
| return expr.Eval(parameters) |
| } |
| } |