Files
Calcium-Ion 4aee5f7d5a feat: better admin permissions (#5755)
* feat: add casbin admin permissions

* feat: improve audit logging to associate logs with actual operators and target users

* feat: enhance admin permissions and UI interactions for sensitive actions

* Refactor authz RBAC and tighten channel permissions

* Split channel authz field policy

* Address channel authz review findings
2026-06-27 17:01:59 +08:00

164 lines
4.4 KiB
Go

package authz
import (
"fmt"
"sort"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/model"
"github.com/casbin/casbin/v2"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type overridePolicy struct {
Resource string
Action string
Effect string
}
func SetUserPermissions(userID int, permissions PermissionsMap) error {
e := currentEnforcer()
if e == nil {
return fmt.Errorf("authz enforcer is not initialized")
}
for resource, actions := range permissions {
if !isKnownResource(resource) {
continue
}
if _, err := e.RemoveFilteredPolicy(0, UserSubject(userID), resource); err != nil {
return err
}
for _, policy := range userOverridePolicies(e, resource, actions) {
if _, err := e.AddPolicy(UserSubject(userID), policy.Resource, policy.Action, policy.Effect); err != nil {
return err
}
}
}
return nil
}
func SetUserPermissionsInTx(tx *gorm.DB, userID int, permissions PermissionsMap) error {
e := currentEnforcer()
if e == nil {
return fmt.Errorf("authz enforcer is not initialized")
}
for resource, actions := range permissions {
if !isKnownResource(resource) {
continue
}
if err := tx.Where("ptype = ? AND v0 = ? AND v1 = ?", "p", UserSubject(userID), resource).Delete(&model.CasbinRule{}).Error; err != nil {
return err
}
policies := userOverridePolicies(e, resource, actions)
if len(policies) == 0 {
continue
}
rules := make([]model.CasbinRule, 0, len(policies))
for _, policy := range policies {
rules = append(rules, newRule("p", []string{UserSubject(userID), policy.Resource, policy.Action, policy.Effect}))
}
if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&rules).Error; err != nil {
return err
}
}
return nil
}
func ClearUserPermissions(userID int) error {
e := currentEnforcer()
if e == nil {
return fmt.Errorf("authz enforcer is not initialized")
}
for _, resource := range registry {
if _, err := e.RemoveFilteredPolicy(0, UserSubject(userID), resource.Resource); err != nil {
return err
}
}
return nil
}
func ClearUserPermissionsInTx(tx *gorm.DB, userID int) error {
for _, resource := range registry {
if err := tx.Where("ptype = ? AND v0 = ? AND v1 = ?", "p", UserSubject(userID), resource.Resource).Delete(&model.CasbinRule{}).Error; err != nil {
return err
}
}
return nil
}
func ClearUserAuthorization(userID int) error {
return ClearUserPermissions(userID)
}
func ClearUserAuthorizationInTx(tx *gorm.DB, userID int) error {
return ClearUserPermissionsInTx(tx, userID)
}
// ExplicitUserPermissions returns the effective permission matrix for the
// managed role plus any per-user overrides.
func ExplicitUserPermissions(userID int) PermissionsMap {
return Capabilities(userID, common.RoleAdminUser)
}
// ExplicitUserOverrides returns only the per-user override entries.
func ExplicitUserOverrides(userID int) PermissionsMap {
e := currentEnforcer()
if e == nil {
return PermissionsMap{}
}
result := PermissionsMap{}
for _, resource := range registry {
policies, err := e.GetFilteredPolicy(0, UserSubject(userID), resource.Resource)
if err != nil {
return PermissionsMap{}
}
actions := make(map[string]bool, len(policies))
for _, policy := range policies {
if len(policy) >= 3 && isKnownPermission(Permission{Resource: policy[1], Action: policy[2]}) {
effect := policyEffect(policy)
if effect == EffectAllow || effect == EffectDeny {
actions[policy[2]] = effect == EffectAllow
}
}
}
if len(actions) > 0 {
result[resource.Resource] = actions
}
}
return result
}
// userOverridePolicies returns the override entries that differ from the managed
// role baseline; entries matching the baseline are omitted.
func userOverridePolicies(e *casbin.SyncedEnforcer, resource string, actions map[string]bool) []overridePolicy {
overrides := make([]overridePolicy, 0, len(actions))
for _, action := range catalogActions(resource) {
desired, ok := actions[action.Action]
if !ok {
continue
}
permission := Permission{Resource: resource, Action: action.Action}
if desired == roleBaselineAllows(e, managedRoleKey, permission) {
continue
}
effect := EffectDeny
if desired {
effect = EffectAllow
}
overrides = append(overrides, overridePolicy{
Resource: resource,
Action: action.Action,
Effect: effect,
})
}
sort.Slice(overrides, func(i, j int) bool {
return overrides[i].Action < overrides[j].Action
})
return overrides
}