* 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
230 lines
7.1 KiB
Go
230 lines
7.1 KiB
Go
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])
|
|
}
|