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:
@@ -0,0 +1,229 @@
|
||||
package authz
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func newAuthzTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
wasMaster := common.IsMasterNode
|
||||
common.IsMasterNode = true
|
||||
t.Cleanup(func() {
|
||||
common.IsMasterNode = wasMaster
|
||||
})
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
sqlDB, err := db.DB()
|
||||
require.NoError(t, err)
|
||||
sqlDB.SetMaxOpenConns(1)
|
||||
require.NoError(t, db.AutoMigrate(&model.CasbinRule{}, &model.AuthzRole{}))
|
||||
return db
|
||||
}
|
||||
|
||||
func TestInitSeedsBuiltInRolesAndPoliciesOnce(t *testing.T) {
|
||||
db := newAuthzTestDB(t)
|
||||
|
||||
require.NoError(t, Init(db))
|
||||
require.NoError(t, Init(db))
|
||||
|
||||
// root is a superuser role and is granted everything implicitly, so only the
|
||||
// admin baseline is written as explicit policy rows.
|
||||
var count int64
|
||||
require.NoError(t, db.Model(&model.CasbinRule{}).Count(&count).Error)
|
||||
assert.Equal(t, int64(len(PermissionsForRole(BuiltInRoleAdmin))), count)
|
||||
|
||||
var roles []model.AuthzRole
|
||||
require.NoError(t, db.Order("sort asc").Find(&roles).Error)
|
||||
require.Len(t, roles, 2)
|
||||
assert.Equal(t, BuiltInRoleRoot, roles[0].Key)
|
||||
assert.Equal(t, BuiltInRoleAdmin, roles[1].Key)
|
||||
|
||||
assert.True(t, Can(1, common.RoleRootUser, ChannelSensitiveWrite))
|
||||
assert.True(t, Can(2, common.RoleAdminUser, ChannelRead))
|
||||
assert.True(t, Can(2, common.RoleAdminUser, ChannelOperate))
|
||||
assert.True(t, Can(2, common.RoleAdminUser, ChannelWrite))
|
||||
assert.False(t, Can(2, common.RoleAdminUser, ChannelSensitiveWrite))
|
||||
assert.False(t, Can(3, common.RoleCommonUser, ChannelRead))
|
||||
}
|
||||
|
||||
func TestInitOnSlaveOnlyLoadsPolicies(t *testing.T) {
|
||||
wasMaster := common.IsMasterNode
|
||||
common.IsMasterNode = false
|
||||
t.Cleanup(func() {
|
||||
common.IsMasterNode = wasMaster
|
||||
})
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
sqlDB, err := db.DB()
|
||||
require.NoError(t, err)
|
||||
sqlDB.SetMaxOpenConns(1)
|
||||
require.NoError(t, db.AutoMigrate(&model.CasbinRule{}, &model.AuthzRole{}))
|
||||
|
||||
require.NoError(t, Init(db))
|
||||
|
||||
var roleCount int64
|
||||
require.NoError(t, db.Model(&model.AuthzRole{}).Count(&roleCount).Error)
|
||||
assert.Equal(t, int64(0), roleCount)
|
||||
var policyCount int64
|
||||
require.NoError(t, db.Model(&model.CasbinRule{}).Count(&policyCount).Error)
|
||||
assert.Equal(t, int64(0), policyCount)
|
||||
assert.False(t, Can(2, common.RoleAdminUser, ChannelRead))
|
||||
}
|
||||
|
||||
func TestSetUserPermissionsStoresOnlyOverrides(t *testing.T) {
|
||||
db := newAuthzTestDB(t)
|
||||
require.NoError(t, Init(db))
|
||||
|
||||
require.NoError(t, SetUserPermissions(42, PermissionsMap{
|
||||
ResourceChannel: {
|
||||
ActionRead: true,
|
||||
ActionOperate: true,
|
||||
ActionWrite: false,
|
||||
ActionSensitiveWrite: true,
|
||||
ActionSecretView: false,
|
||||
"unknown": true,
|
||||
},
|
||||
"unknown": {
|
||||
ActionRead: true,
|
||||
},
|
||||
}))
|
||||
|
||||
assert.True(t, Can(42, common.RoleAdminUser, ChannelSensitiveWrite))
|
||||
assert.False(t, Can(42, common.RoleAdminUser, ChannelWrite))
|
||||
assert.Equal(t, PermissionsMap{
|
||||
ResourceChannel: {
|
||||
ActionRead: true,
|
||||
ActionOperate: true,
|
||||
ActionWrite: false,
|
||||
ActionSensitiveWrite: true,
|
||||
ActionSecretView: false,
|
||||
},
|
||||
}, ExplicitUserPermissions(42))
|
||||
assert.Equal(t, PermissionsMap{
|
||||
ResourceChannel: {
|
||||
ActionSensitiveWrite: true,
|
||||
ActionWrite: false,
|
||||
},
|
||||
}, ExplicitUserOverrides(42))
|
||||
|
||||
var userPolicyCount int64
|
||||
require.NoError(t, db.Model(&model.CasbinRule{}).Where("v0 = ?", UserSubject(42)).Count(&userPolicyCount).Error)
|
||||
assert.Equal(t, int64(2), userPolicyCount)
|
||||
|
||||
require.NoError(t, SetUserPermissions(42, PermissionsMap{ResourceChannel: {
|
||||
ActionRead: true,
|
||||
ActionOperate: true,
|
||||
ActionWrite: true,
|
||||
ActionSensitiveWrite: false,
|
||||
ActionSecretView: false,
|
||||
}}))
|
||||
assert.False(t, Can(42, common.RoleAdminUser, ChannelSensitiveWrite))
|
||||
assert.Equal(t, PermissionsMap{
|
||||
ResourceChannel: {
|
||||
ActionRead: true,
|
||||
ActionOperate: true,
|
||||
ActionWrite: true,
|
||||
ActionSensitiveWrite: false,
|
||||
ActionSecretView: false,
|
||||
},
|
||||
}, ExplicitUserPermissions(42))
|
||||
assert.Empty(t, ExplicitUserOverrides(42))
|
||||
}
|
||||
|
||||
func TestClearUserAuthorizationRemovesOverrides(t *testing.T) {
|
||||
db := newAuthzTestDB(t)
|
||||
require.NoError(t, Init(db))
|
||||
|
||||
require.NoError(t, SetUserPermissions(90, PermissionsMap{ResourceChannel: {
|
||||
ActionWrite: false,
|
||||
ActionSensitiveWrite: true,
|
||||
}}))
|
||||
|
||||
assert.True(t, Can(90, common.RoleAdminUser, ChannelSensitiveWrite))
|
||||
assert.False(t, Can(90, common.RoleAdminUser, ChannelWrite))
|
||||
|
||||
require.NoError(t, ClearUserAuthorization(90))
|
||||
|
||||
assert.Empty(t, ExplicitUserOverrides(90))
|
||||
assert.True(t, Can(90, common.RoleAdminUser, ChannelRead))
|
||||
assert.True(t, Can(90, common.RoleAdminUser, ChannelWrite))
|
||||
assert.False(t, Can(90, common.RoleAdminUser, ChannelSensitiveWrite))
|
||||
assert.False(t, Can(90, common.RoleCommonUser, ChannelRead))
|
||||
}
|
||||
|
||||
func TestSetUserPermissionsInTxDoesNotMutateEnforcerBeforeReload(t *testing.T) {
|
||||
db := newAuthzTestDB(t)
|
||||
require.NoError(t, Init(db))
|
||||
|
||||
require.NoError(t, db.Transaction(func(tx *gorm.DB) error {
|
||||
return SetUserPermissionsInTx(tx, 42, PermissionsMap{ResourceChannel: {
|
||||
ActionRead: true,
|
||||
ActionOperate: true,
|
||||
ActionWrite: true,
|
||||
ActionSensitiveWrite: true,
|
||||
ActionSecretView: false,
|
||||
}})
|
||||
}))
|
||||
|
||||
assert.False(t, Can(42, common.RoleAdminUser, ChannelSensitiveWrite))
|
||||
require.NoError(t, ReloadPolicy())
|
||||
assert.True(t, Can(42, common.RoleAdminUser, ChannelSensitiveWrite))
|
||||
}
|
||||
|
||||
func TestSetUserPermissionsInTxRollbackLeavesNoPolicy(t *testing.T) {
|
||||
db := newAuthzTestDB(t)
|
||||
require.NoError(t, Init(db))
|
||||
|
||||
tx := db.Begin()
|
||||
require.NoError(t, tx.Error)
|
||||
require.NoError(t, SetUserPermissionsInTx(tx, 43, PermissionsMap{ResourceChannel: {
|
||||
ActionSensitiveWrite: true,
|
||||
}}))
|
||||
require.NoError(t, tx.Rollback().Error)
|
||||
require.NoError(t, ReloadPolicy())
|
||||
|
||||
assert.False(t, Can(43, common.RoleAdminUser, ChannelSensitiveWrite))
|
||||
var count int64
|
||||
require.NoError(t, db.Model(&model.CasbinRule{}).Where("v0 = ?", UserSubject(43)).Count(&count).Error)
|
||||
assert.Equal(t, int64(0), count)
|
||||
}
|
||||
|
||||
func TestAdapterAddPolicyIsIdempotent(t *testing.T) {
|
||||
db := newAuthzTestDB(t)
|
||||
adapter := newGormAdapter(db)
|
||||
rule := []string{UserSubject(55), ResourceChannel, ActionSensitiveWrite, EffectAllow}
|
||||
|
||||
require.NoError(t, adapter.AddPolicy("p", "p", rule))
|
||||
require.NoError(t, adapter.AddPolicy("p", "p", rule))
|
||||
|
||||
var count int64
|
||||
require.NoError(t, db.Model(&model.CasbinRule{}).Where(
|
||||
"ptype = ? AND v0 = ? AND v1 = ? AND v2 = ? AND v3 = ?",
|
||||
"p",
|
||||
UserSubject(55),
|
||||
ResourceChannel,
|
||||
ActionSensitiveWrite,
|
||||
EffectAllow,
|
||||
).Count(&count).Error)
|
||||
assert.Equal(t, int64(1), count)
|
||||
}
|
||||
|
||||
func TestCapabilitiesUseCatalogShape(t *testing.T) {
|
||||
db := newAuthzTestDB(t)
|
||||
require.NoError(t, Init(db))
|
||||
|
||||
capabilities := Capabilities(7, common.RoleAdminUser)
|
||||
|
||||
assert.True(t, capabilities[ResourceChannel][ActionRead])
|
||||
assert.True(t, capabilities[ResourceChannel][ActionOperate])
|
||||
assert.True(t, capabilities[ResourceChannel][ActionWrite])
|
||||
assert.False(t, capabilities[ResourceChannel][ActionSensitiveWrite])
|
||||
assert.False(t, capabilities[ResourceChannel][ActionSecretView])
|
||||
}
|
||||
Reference in New Issue
Block a user