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
This commit is contained in:
+82
-9
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/QuantumNous/new-api/logger"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
"github.com/QuantumNous/new-api/service"
|
||||
"github.com/QuantumNous/new-api/service/authz"
|
||||
"github.com/QuantumNous/new-api/setting"
|
||||
"github.com/QuantumNous/new-api/setting/operation_setting"
|
||||
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LoginRequest struct {
|
||||
@@ -334,6 +336,7 @@ func GetUser(c *gin.Context) {
|
||||
common.ApiErrorI18n(c, i18n.MsgUserNoPermissionSameLevel)
|
||||
return
|
||||
}
|
||||
user.AdminPermissions = authz.Capabilities(user.Id, user.Role)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
@@ -443,6 +446,7 @@ func GetSelf(c *gin.Context) {
|
||||
|
||||
// 计算用户权限信息
|
||||
permissions := calculateUserPermissions(userRole)
|
||||
permissions["admin_permissions"] = authz.Capabilities(id, userRole)
|
||||
|
||||
// 获取用户设置并提取sidebar_modules
|
||||
userSetting := user.GetSetting()
|
||||
@@ -620,23 +624,41 @@ func UpdateUser(c *gin.Context) {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
if updatedUser.Role != common.RoleGuestUser && updatedUser.Role != originUser.Role {
|
||||
common.ApiErrorI18n(c, i18n.MsgInvalidParams)
|
||||
return
|
||||
}
|
||||
updatedUser.Role = originUser.Role
|
||||
myRole := c.GetInt("role")
|
||||
if !canManageTargetRole(myRole, originUser.Role) {
|
||||
common.ApiErrorI18n(c, i18n.MsgUserNoPermissionHigherLevel)
|
||||
return
|
||||
}
|
||||
if !canManageTargetRole(myRole, updatedUser.Role) {
|
||||
common.ApiErrorI18n(c, i18n.MsgUserCannotCreateHigherLevel)
|
||||
return
|
||||
}
|
||||
if updatedUser.Password == "$I_LOVE_U" {
|
||||
updatedUser.Password = "" // rollback to what it should be
|
||||
}
|
||||
updatePassword := updatedUser.Password != ""
|
||||
if err := updatedUser.Edit(updatePassword); err != nil {
|
||||
authzTouched := false
|
||||
if err := model.DB.Transaction(func(tx *gorm.DB) error {
|
||||
if err := updatedUser.EditWithTx(tx, updatePassword); err != nil {
|
||||
return err
|
||||
}
|
||||
touched, err := updateAdminPermissionsForUserInTx(c, tx, updatedUser.Id, originUser.Role, updatedUser.AdminPermissions)
|
||||
authzTouched = touched
|
||||
return err
|
||||
}); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
if authzTouched {
|
||||
if err := authz.ReloadPolicy(); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := model.InvalidateUserCache(updatedUser.Id); err != nil {
|
||||
common.SysLog(fmt.Sprintf("failed to invalidate user cache for user %d: %s", updatedUser.Id, err.Error()))
|
||||
}
|
||||
recordManageAuditFor(c, updatedUser.Id, "user.update", map[string]interface{}{
|
||||
"username": originUser.Username,
|
||||
"id": updatedUser.Id,
|
||||
@@ -901,10 +923,25 @@ func CreateUser(c *gin.Context) {
|
||||
DisplayName: user.DisplayName,
|
||||
Role: user.Role, // 保持管理员设置的角色
|
||||
}
|
||||
if err := cleanUser.Insert(0); err != nil {
|
||||
authzTouched := false
|
||||
if err := model.DB.Transaction(func(tx *gorm.DB) error {
|
||||
if err := cleanUser.InsertWithTx(tx, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
touched, err := updateAdminPermissionsForUserInTx(c, tx, cleanUser.Id, cleanUser.Role, user.AdminPermissions)
|
||||
authzTouched = touched
|
||||
return err
|
||||
}); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
if authzTouched {
|
||||
if err := authz.ReloadPolicy(); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
cleanUser.FinishInsert(0)
|
||||
|
||||
recordManageAuditFor(c, cleanUser.Id, "user.create", map[string]interface{}{
|
||||
"username": cleanUser.Username,
|
||||
@@ -917,6 +954,22 @@ func CreateUser(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
func updateAdminPermissionsForUserInTx(c *gin.Context, tx *gorm.DB, userID int, userRole int, permissions map[string]map[string]bool) (bool, error) {
|
||||
if permissions == nil {
|
||||
if userRole < common.RoleAdminUser && c.GetInt("role") == common.RoleRootUser {
|
||||
return true, authz.ClearUserAuthorizationInTx(tx, userID)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
if c.GetInt("role") != common.RoleRootUser {
|
||||
return false, fmt.Errorf("only root can update admin permissions")
|
||||
}
|
||||
if userRole < common.RoleAdminUser {
|
||||
return true, authz.ClearUserAuthorizationInTx(tx, userID)
|
||||
}
|
||||
return true, authz.SetUserPermissionsInTx(tx, userID, permissions)
|
||||
}
|
||||
|
||||
type ManageRequest struct {
|
||||
Id int `json:"id"`
|
||||
Action string `json:"action"`
|
||||
@@ -1040,9 +1093,29 @@ func ManageUser(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := user.Update(false); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
authzTouched := false
|
||||
if req.Action == "demote" {
|
||||
if err := model.DB.Transaction(func(tx *gorm.DB) error {
|
||||
if err := user.UpdateWithTx(tx, false); err != nil {
|
||||
return err
|
||||
}
|
||||
authzTouched = true
|
||||
return authz.ClearUserAuthorizationInTx(tx, user.Id)
|
||||
}); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
if authzTouched {
|
||||
if err := authz.ReloadPolicy(); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := user.Update(false); err != nil {
|
||||
common.ApiError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 禁用 / 角色调整后,强制失效用户缓存与其全部令牌缓存,
|
||||
// 避免在 Redis TTL 过期前仍使用旧状态(尤其是禁用后仍可发起请求的问题)。
|
||||
|
||||
Reference in New Issue
Block a user