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, }, ResourceTaskPlugin: { ActionBind: 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, }, ResourceTaskPlugin: { ActionBind: 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]) assert.False(t, capabilities[ResourceTaskPlugin][ActionBind]) } func TestTaskPluginBindIsRootOnlyUntilGranted(t *testing.T) { db := newAuthzTestDB(t) require.NoError(t, Init(db)) var bindAction *ActionDefinition for _, resource := range Catalog() { if resource.Resource != ResourceTaskPlugin { continue } assert.Equal(t, "Task Plugin", resource.LabelKey) for i := range resource.Actions { if resource.Actions[i].Action == ActionBind { bindAction = &resource.Actions[i] } } } require.NotNil(t, bindAction) assert.Equal(t, "Bind task plugins", bindAction.LabelKey) assert.Equal(t, "List registered task plugins and bind them when creating or editing task plugin channels.", bindAction.DescriptionKey) assert.Empty(t, bindAction.DefaultRoles) assert.False(t, Can(2, common.RoleAdminUser, TaskPluginBind)) assert.True(t, Can(1, common.RoleRootUser, TaskPluginBind)) enforcer := currentEnforcer() require.NotNil(t, enforcer) _, err := enforcer.AddPolicy(RoleSubject(BuiltInRoleAdmin), ResourceTaskPlugin, ActionBind, EffectAllow) require.NoError(t, err) assert.True(t, Can(2, common.RoleAdminUser, TaskPluginBind)) _, err = enforcer.RemovePolicy(RoleSubject(BuiltInRoleAdmin), ResourceTaskPlugin, ActionBind, EffectAllow) require.NoError(t, err) assert.False(t, Can(2, common.RoleAdminUser, TaskPluginBind)) }