Files
casdoor/object/permission_enforcer.go

515 lines
13 KiB
Go

// Copyright 2021 The Casdoor 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 object
import (
"fmt"
"strings"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/config"
"github.com/casbin/casbin/v2/log"
"github.com/casbin/casbin/v2/model"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3"
)
func getPermissionEnforcer(p *Permission, permissionIDs ...string) (*casbin.Enforcer, error) {
// Init an enforcer instance without specifying a model or adapter.
// If you specify an adapter, it will load all policies, which is a
// heavy process that can slow down the application.
enforcer, err := casbin.NewEnforcer(&log.DefaultLogger{}, false)
if err != nil {
return nil, err
}
err = p.setEnforcerModel(enforcer)
if err != nil {
return nil, err
}
err = p.setEnforcerAdapter(enforcer)
if err != nil {
return nil, err
}
policyFilterV5 := []string{p.GetId()}
if len(permissionIDs) != 0 {
policyFilterV5 = permissionIDs
}
policyFilter := xormadapter.Filter{
// Permission enforcers only persist p rules. Legacy g rows are rebuilt from roles at runtime.
Ptype: []string{"p"},
V5: policyFilterV5,
}
err = enforcer.LoadFilteredPolicy(policyFilter)
if err != nil {
return nil, err
}
// we can rebuild group policies in memory
err = loadRuntimeGroupingPolicies(enforcer, p, permissionIDs...)
if err != nil {
return nil, err
}
return enforcer, nil
}
func (p *Permission) setEnforcerAdapter(enforcer *casbin.Enforcer) error {
tableName := "permission_rule"
if len(p.Adapter) != 0 {
adapterObj, err := getAdapter(p.Owner, p.Adapter)
if err != nil {
return err
}
if adapterObj != nil && adapterObj.Table != "" {
tableName = adapterObj.Table
}
}
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
adapter, err := xormadapter.NewAdapterByEngineWithTableName(ormer.Engine, tableName, tableNamePrefix)
if err != nil {
return err
}
enforcer.SetAdapter(adapter)
return nil
}
func (p *Permission) setEnforcerModel(enforcer *casbin.Enforcer) error {
var permissionModel *Model
var err error
if p.Model != "" {
permissionModel, err = GetModel(p.Model)
if err != nil {
return err
}
}
// TODO: return error if permissionModel is nil.
m := model.Model{}
if permissionModel != nil {
m, err = GetBuiltInModel(permissionModel.ModelText)
} else {
m, err = GetBuiltInModel("")
}
if err != nil {
return err
}
err = enforcer.InitWithModelAndAdapter(m, nil)
if err != nil {
return err
}
return nil
}
func getPolicies(permission *Permission) [][]string {
var policies [][]string
permissionId := permission.GetId()
domainExist := len(permission.Domains) > 0
usersAndRoles := append(permission.Users, permission.Roles...)
for _, userOrRole := range usersAndRoles {
for _, resource := range permission.Resources {
for _, action := range permission.Actions {
if domainExist {
for _, domain := range permission.Domains {
policies = append(policies, []string{userOrRole, domain, resource, action, strings.ToLower(permission.Effect), permissionId})
}
} else {
policies = append(policies, []string{userOrRole, resource, action, strings.ToLower(permission.Effect), "", permissionId})
}
}
}
}
return policies
}
type permissionRoleResolver struct {
rolesByOwner map[string][]*Role
roleByID map[string]*Role
}
func newPermissionRoleResolver() *permissionRoleResolver {
return &permissionRoleResolver{
rolesByOwner: map[string][]*Role{},
roleByID: map[string]*Role{},
}
}
func (r *permissionRoleResolver) getRoles(owner string) ([]*Role, error) {
if roles, ok := r.rolesByOwner[owner]; ok {
return roles, nil
}
roles, err := GetRoles(owner)
if err != nil {
return nil, err
}
r.rolesByOwner[owner] = roles
for _, role := range roles {
r.roleByID[role.GetId()] = role
}
return roles, nil
}
func (r *permissionRoleResolver) getRolesInRole(permissionOwner string, roleId string, visited map[string]struct{}) ([]*Role, error) {
if roleId == "*" {
roleId = util.GetId(permissionOwner, "*")
}
roleOwner, roleName, err := util.GetOwnerAndNameFromIdWithError(roleId)
if err != nil {
return []*Role{}, err
}
if roleName == "*" {
roles, err := r.getRoles(roleOwner)
if err != nil {
return []*Role{}, err
}
return roles, nil
}
_, err = r.getRoles(roleOwner)
if err != nil {
return []*Role{}, err
}
role := r.roleByID[roleId]
if role == nil {
return []*Role{}, nil
}
visited[roleId] = struct{}{}
roles := []*Role{role}
for _, subRole := range role.Roles {
if _, ok := visited[subRole]; !ok {
subRoles, err := r.getRolesInRole(roleOwner, subRole, visited)
if err != nil {
return []*Role{}, err
}
roles = append(roles, subRoles...)
}
}
return roles, nil
}
func getPermissionEnforcerTargets(permission *Permission, permissionIDs ...string) ([]*Permission, error) {
if len(permissionIDs) == 0 {
return []*Permission{permission}, nil
}
permissions := make([]*Permission, 0, len(permissionIDs))
visited := map[string]struct{}{}
for _, permissionID := range permissionIDs {
if _, ok := visited[permissionID]; ok {
continue
}
targetPermission, err := GetPermission(permissionID)
if err != nil {
return nil, err
}
if targetPermission == nil {
return nil, fmt.Errorf("the permission: %s doesn't exist", permissionID)
}
permissions = append(permissions, targetPermission)
visited[permissionID] = struct{}{}
}
return permissions, nil
}
func newRuntimeGroupingPolicy(sub string, roleId string, domain string) []string {
return []string{sub, roleId, domain, "", "", ""}
}
func appendRuntimeGroupingPolicy(groupingPolicies *[][]string, visited map[string]struct{}, rule []string) {
// we can't use []string as key, so use null character
key := strings.Join(rule, "\x00")
if _, ok := visited[key]; ok {
return
}
*groupingPolicies = append(*groupingPolicies, rule)
visited[key] = struct{}{}
}
func getRuntimeGroupingPolicies(permissions []*Permission) ([][]string, error) {
var groupingPolicies [][]string
visitedPolicies := map[string]struct{}{}
roleResolver := newPermissionRoleResolver()
for _, permission := range permissions {
domainExist := len(permission.Domains) > 0
for _, roleId := range permission.Roles {
visited := map[string]struct{}{}
rolesInRole, err := roleResolver.getRolesInRole(permission.Owner, roleId, visited)
if err != nil {
return nil, err
}
for _, role := range rolesInRole {
currentRoleID := role.GetId()
for _, subUser := range role.Users {
if domainExist {
for _, domain := range permission.Domains {
appendRuntimeGroupingPolicy(&groupingPolicies, visitedPolicies, newRuntimeGroupingPolicy(subUser, currentRoleID, domain))
}
} else {
appendRuntimeGroupingPolicy(&groupingPolicies, visitedPolicies, newRuntimeGroupingPolicy(subUser, currentRoleID, ""))
}
}
for _, subRole := range role.Roles {
if domainExist {
for _, domain := range permission.Domains {
appendRuntimeGroupingPolicy(&groupingPolicies, visitedPolicies, newRuntimeGroupingPolicy(subRole, currentRoleID, domain))
}
} else {
appendRuntimeGroupingPolicy(&groupingPolicies, visitedPolicies, newRuntimeGroupingPolicy(subRole, currentRoleID, ""))
}
}
}
}
}
return groupingPolicies, nil
}
func loadRuntimeGroupingPolicies(enforcer *casbin.Enforcer, permission *Permission, permissionIDs ...string) error {
if !HasRoleDefinition(enforcer.GetModel()) {
return nil
}
targetPermissions, err := getPermissionEnforcerTargets(permission, permissionIDs...)
if err != nil {
return err
}
groupingPolicies, err := getRuntimeGroupingPolicies(targetPermissions)
if err != nil {
return err
}
if len(groupingPolicies) == 0 {
return nil
}
enforcer.EnableAutoSave(false)
defer enforcer.EnableAutoSave(true)
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
if err != nil {
return err
}
return nil
}
func addPolicies(permission *Permission) error {
enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return err
}
policies := getPolicies(permission)
_, err = enforcer.AddPolicies(policies)
return err
}
func removePolicies(permission *Permission) error {
enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return err
}
policies := getPolicies(permission)
_, err = enforcer.RemovePolicies(policies)
return err
}
func Enforce(permission *Permission, request []interface{}, permissionIds ...string) (bool, error) {
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
if err != nil {
return false, err
}
// Convert each element: JSON-object strings and maps become anonymous structs
// so Casbin can evaluate ABAC rules with dot-notation (e.g. r.sub.DivisionGuid).
interfaceRequest := util.InterfaceToEnforceArray(request)
return enforcer.Enforce(interfaceRequest...)
}
func BatchEnforce(permission *Permission, requests [][]interface{}, permissionIds ...string) ([]bool, error) {
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
if err != nil {
return nil, err
}
// Convert each element in every row for ABAC support.
interfaceRequests := util.InterfaceToEnforceArray2d(requests)
return enforcer.BatchEnforce(interfaceRequests)
}
func getEnforcers(userId string) ([]*casbin.Enforcer, error) {
permissions, _, err := getPermissionsAndRolesByUser(userId)
if err != nil {
return nil, err
}
allRoles, err := GetAllRoles(userId)
if err != nil {
return nil, err
}
for _, role := range allRoles {
var permissionsByRole []*Permission
permissionsByRole, err = GetPermissionsByRole(role)
if err != nil {
return nil, err
}
permissions = append(permissions, permissionsByRole...)
}
var enforcers []*casbin.Enforcer
for _, permission := range permissions {
var enforcer *casbin.Enforcer
enforcer, err = getPermissionEnforcer(permission)
if err != nil {
return nil, err
}
enforcers = append(enforcers, enforcer)
}
return enforcers, nil
}
func GetAllObjects(userId string) ([]string, error) {
enforcers, err := getEnforcers(userId)
if err != nil {
return nil, err
}
res := []string{}
for _, enforcer := range enforcers {
items := enforcer.GetAllObjects()
res = append(res, items...)
}
return res, nil
}
func GetAllActions(userId string) ([]string, error) {
enforcers, err := getEnforcers(userId)
if err != nil {
return nil, err
}
res := []string{}
for _, enforcer := range enforcers {
items := enforcer.GetAllActions()
res = append(res, items...)
}
return res, nil
}
func GetAllRoles(userId string) ([]string, error) {
roles, err := getRolesByUser(userId)
if err != nil {
return nil, err
}
res := []string{}
for _, role := range roles {
res = append(res, role.Name)
}
return res, nil
}
func GetBuiltInModel(modelText string) (model.Model, error) {
if modelText == "" {
modelText = `[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act, eft, "", permissionId
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`
return model.NewModelFromString(modelText)
} else {
cfg, err := config.NewConfigFromText(modelText)
if err != nil {
return nil, err
}
// load [policy_definition]
policyDefinition := strings.Split(cfg.String("policy_definition::p"), ",")
fieldsNum := len(policyDefinition)
if fieldsNum > builtInMaxFields {
return nil, fmt.Errorf("the maximum policy_definition field number cannot exceed %d, got %d", builtInMaxFields, fieldsNum)
}
// filled empty field with "" and V5 with "permissionId"
if fieldsNum == builtInMaxFields {
sixthField := strings.TrimSpace(policyDefinition[builtInMaxFields-1])
if sixthField != "permissionId" {
return nil, fmt.Errorf("when adding policies with permissions, the sixth field of policy_definition must be permissionId, got %s", policyDefinition[builtInMaxFields-1])
}
} else {
needFill := builtInMaxFields - fieldsNum
for i := 0; i < needFill-1; i++ {
policyDefinition = append(policyDefinition, "")
}
policyDefinition = append(policyDefinition, "permissionId")
}
m, err := model.NewModelFromString(modelText)
if err != nil {
return nil, err
}
m.AddDef("p", "p", strings.Join(policyDefinition, ","))
return m, err
}
}