* 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
164 lines
4.4 KiB
Go
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
|
|
}
|