mirror of
https://github.com/mattermost/mattermost.git
synced 2026-04-13 04:57:45 -04:00
* ci: enable fullyparallel mode for server tests Replace os.Setenv, os.Chdir, and global state mutations with parallel-safe alternatives (t.Setenv, t.Chdir, test hooks) across 37 files. Refactor GetLogRootPath and MM_INSTALL_TYPE to use package-level test hooks instead of environment variables. This enables gotestsum --fullparallel, allowing all test packages to run with maximum parallelism within each shard. Co-authored-by: Claude <claude@anthropic.com> * ci: split fullyparallel from continue-on-error in workflow template - Add new boolean input 'allow-failure' separate from 'fullyparallel' - Change continue-on-error to use allow-failure instead of fullyparallel - Update server-ci.yml to pass allow-failure: true for test coverage job - Allows independent control of parallel execution and failure tolerance Co-authored-by: Claude <claude@anthropic.com> * fix: protect TestOverrideLogRootPath with sync.Mutex for parallel tests - Replace global var TestOverrideLogRootPath with mutex-protected functions - Add SetTestOverrideLogRootPath() and getTestOverrideLogRootPath() functions - Update GetLogRootPath() to use thread-safe getter - Update all test files to use SetTestOverrideLogRootPath() with t.Cleanup() - Fixes race condition when running tests with t.Parallel() Co-authored-by: Claude <claude@anthropic.com> * fix: configure audit settings before server setup in tests - Move ExperimentalAuditSettings from UpdateConfig() to config defaults - Pass audit config via app.Config() option in SetupWithServerOptions() - Fixes audit test setup ordering to configure BEFORE server initialization - Resolves CodeRabbit's audit config timing issue in api4 tests Co-authored-by: Claude <claude@anthropic.com> * fix: implement SetTestOverrideLogRootPath mutex in logger.go The previous commit updated test callers to use SetTestOverrideLogRootPath() but didn't actually create the function in config/logger.go, causing build failures across all CI shards. This commit: - Replaces the exported var TestOverrideLogRootPath with mutex-protected unexported state (testOverrideLogRootPath + testOverrideLogRootMu) - Adds exported SetTestOverrideLogRootPath() setter - Adds unexported getTestOverrideLogRootPath() getter - Updates GetLogRootPath() to use the thread-safe getter - Fixes log_test.go callers that were missed in the previous commit Co-authored-by: Claude <claude@anthropic.com> * fix(test): use SetupConfig for access_control feature flag registration InitAccessControlPolicy() checks FeatureFlags.AttributeBasedAccessControl at route registration time during server startup. Setting the flag via UpdateConfig after Setup() is too late — routes are never registered and API calls return 404. Use SetupConfig() to pass the feature flag in the initial config before server startup, ensuring routes are properly registered. Co-authored-by: Claude <claude@anthropic.com> * fix(test): restore BurnOnRead flag state in TestRevealPost subtest The 'feature not enabled' subtest disables BurnOnRead without restoring it via t.Cleanup. Subsequent subtests inherit the disabled state, which can cause 501 errors when they expect the feature to be available. Add t.Cleanup to restore FeatureFlags.BurnOnRead = true after the subtest completes. Co-authored-by: Claude <claude@anthropic.com> * fix(test): restore EnableSharedChannelsMemberSync flag via t.Cleanup The test disables EnableSharedChannelsMemberSync without restoring it. If the subtest exits early (e.g., require failure), later sibling subtests inherit a disabled flag and become flaky. Add t.Cleanup to restore the flag after the subtest completes. Co-authored-by: Claude <claude@anthropic.com> * Fix test parallelism: use instance-scoped overrides and init-time audit config Replace package-level test globals (TestOverrideInstallType, SetTestOverrideLogRootPath) with fields on PlatformService so each test gets its own instance without process-wide mutation. Fix three audit tests (TestUserLoginAudit, TestLogoutAuditAuthStatus, TestUpdatePasswordAudit) that configured the audit logger after server init — the audit logger only reads config at startup, so pass audit settings via app.Config() at init time instead. Also revert the Go 1.24.13 downgrade and bump mattermost-govet to v2.0.2 for Go 1.25.8 compatibility. * Fix audit unit tests * Fix MMCLOUDURL unit tests * Fixed unit tests using MM_NOTIFY_ADMIN_COOL_OFF_DAYS * Make app migrations idempotent for parallel test safety Change System().Save() to System().SaveOrUpdate() in all migration completion markers. When two parallel tests share a database pool entry, both may race through the check-then-insert migration pattern. Save() causes a duplicate key fatal crash; SaveOrUpdate() makes the second write a harmless no-op. * test: address review feedback on fullyparallel PR - Use SetLogRootPathOverride() setter instead of direct field access in platform/support_packet_test.go and platform/log_test.go (pvev) - Restore TestGetLogRootPath in config/logger_test.go to keep MM_LOG_PATH env var coverage; test uses t.Setenv so it runs serially which is fine (pvev) - Fix misleading comment in config_test.go: code uses t.Setenv, not os.Setenv (jgheithcock) Co-authored-by: Claude <claude@anthropic.com> * fix: add missing os import in post_test.go The os import was dropped during a merge conflict resolution while burn-on-read shared channel tests from master still use os.Setenv. Co-authored-by: Claude <claude@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com> Co-authored-by: wiggin77 <wiggin77@warpmail.net> Co-authored-by: Mattermost Build <build@mattermost.com>
998 lines
40 KiB
Go
998 lines
40 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package api4
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/public/plugin/plugintest/mock"
|
|
"github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestCreateAccessControlPolicy(t *testing.T) {
|
|
th := SetupConfig(t, func(cfg *model.Config) { cfg.FeatureFlags.AttributeBasedAccessControl = true }).InitBasic(t)
|
|
|
|
samplePolicy := &model.AccessControlPolicy{
|
|
ID: th.BasicChannel.Id,
|
|
Type: model.AccessControlPolicyTypeChannel,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("CreateAccessControlPolicy without license", func(t *testing.T) {
|
|
_, resp, err := th.SystemAdminClient.CreateAccessControlPolicy(context.Background(), samplePolicy)
|
|
require.Error(t, err)
|
|
CheckNotImplementedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("CreateAccessControlPolicy with regular user", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
// Create another user who will create the channel
|
|
channelCreator := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, channelCreator, th.BasicTeam)
|
|
channelCreatorClient := th.CreateClient()
|
|
_, _, err := channelCreatorClient.Login(context.Background(), channelCreator.Email, channelCreator.Password)
|
|
require.NoError(t, err)
|
|
|
|
// Create a private channel with the other user (not th.BasicUser)
|
|
privateChannel, _, err := channelCreatorClient.CreateChannel(context.Background(), &model.Channel{
|
|
TeamId: th.BasicTeam.Id,
|
|
Name: "private-channel-" + model.NewId(),
|
|
DisplayName: "Private Channel",
|
|
Type: model.ChannelTypePrivate,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Create channel-specific policy (regular user should not have permission)
|
|
channelPolicy := &model.AccessControlPolicy{
|
|
ID: privateChannel.Id, // Set to actual channel ID
|
|
Type: model.AccessControlPolicyTypeChannel,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create and set up the mock
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
_, resp, err := th.Client.CreateAccessControlPolicy(context.Background(), channelPolicy)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("CreateAccessControlPolicy with channel admin for their channel", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
// Add the permission to channel admin role
|
|
th.AddPermissionToRole(t, model.PermissionManageChannelAccessRules.Id, model.ChannelAdminRoleId)
|
|
|
|
// Create a private channel and make user channel admin
|
|
privateChannel := th.CreatePrivateChannel(t)
|
|
channelAdmin := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, channelAdmin, th.BasicTeam)
|
|
th.AddUserToChannel(t, channelAdmin, privateChannel)
|
|
th.MakeUserChannelAdmin(t, channelAdmin, privateChannel)
|
|
channelAdminClient := th.CreateClient()
|
|
th.LoginBasicWithClient(t, channelAdminClient)
|
|
_, _, err := channelAdminClient.Login(context.Background(), channelAdmin.Email, channelAdmin.Password)
|
|
require.NoError(t, err)
|
|
|
|
// Create channel-specific policy
|
|
channelPolicy := &model.AccessControlPolicy{
|
|
ID: privateChannel.Id,
|
|
Type: model.AccessControlPolicyTypeChannel,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Create and set up the mock
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
mockAccessControlService.On("SavePolicy", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.AccessControlPolicy")).Return(channelPolicy, nil).Times(1)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
_, resp, err := channelAdminClient.CreateAccessControlPolicy(context.Background(), channelPolicy)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
})
|
|
|
|
t.Run("CreateAccessControlPolicy with channel admin for another channel should fail", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
// Create two private channels
|
|
privateChannel1 := th.CreatePrivateChannel(t)
|
|
privateChannel2 := th.CreatePrivateChannel(t)
|
|
channelAdmin := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, channelAdmin, th.BasicTeam)
|
|
th.AddUserToChannel(t, channelAdmin, privateChannel1)
|
|
th.MakeUserChannelAdmin(t, channelAdmin, privateChannel1)
|
|
channelAdminClient := th.CreateClient()
|
|
th.LoginBasicWithClient(t, channelAdminClient)
|
|
_, _, err := channelAdminClient.Login(context.Background(), channelAdmin.Email, channelAdmin.Password)
|
|
require.NoError(t, err)
|
|
|
|
// Try to create policy for different channel
|
|
channelPolicy := &model.AccessControlPolicy{
|
|
ID: privateChannel2.Id,
|
|
Type: model.AccessControlPolicyTypeChannel,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
_, resp, err := channelAdminClient.CreateAccessControlPolicy(context.Background(), channelPolicy)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("CreateAccessControlPolicy with channel admin creating parent policy should fail", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
// Create a private channel and make user channel admin
|
|
privateChannel := th.CreatePrivateChannel(t)
|
|
channelAdmin := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, channelAdmin, th.BasicTeam)
|
|
th.AddUserToChannel(t, channelAdmin, privateChannel)
|
|
th.MakeUserChannelAdmin(t, channelAdmin, privateChannel)
|
|
channelAdminClient := th.CreateClient()
|
|
th.LoginBasicWithClient(t, channelAdminClient)
|
|
_, _, err := channelAdminClient.Login(context.Background(), channelAdmin.Email, channelAdmin.Password)
|
|
require.NoError(t, err)
|
|
|
|
// Try to create parent-type policy
|
|
parentPolicy := &model.AccessControlPolicy{
|
|
ID: model.NewId(),
|
|
Type: model.AccessControlPolicyTypeParent,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
_, resp, err := channelAdminClient.CreateAccessControlPolicy(context.Background(), parentPolicy)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
// Set up a test license with Data Retention enabled
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
// Create and set up the mock
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
|
|
// Set up mock expectations
|
|
mockAccessControlService.On("SavePolicy", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.AccessControlPolicy")).Return(samplePolicy, nil).Times(1)
|
|
|
|
// Set the mock on the app
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
_, resp, err := client.CreateAccessControlPolicy(context.Background(), samplePolicy)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
}, "CreateAccessControlPolicy with system admin")
|
|
|
|
t.Run("CreateAccessControlPolicy with channel scope permissions", func(t *testing.T) {
|
|
// Set up a test license with Data Retention enabled
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
// Create and set up the mock
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
|
|
ch := th.CreatePrivateChannel(t)
|
|
|
|
// Set up mock expectations
|
|
mockAccessControlService.On("SavePolicy", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.AccessControlPolicy")).Return(samplePolicy, nil).Times(1)
|
|
|
|
// Set the mock on the app
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
th.AddPermissionToRole(t, model.PermissionManageChannelAccessRules.Id, model.ChannelAdminRoleId)
|
|
|
|
channelPolicy := &model.AccessControlPolicy{
|
|
Type: model.AccessControlPolicyTypeChannel,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
ID: ch.Id,
|
|
}
|
|
|
|
_, resp, err := th.Client.CreateAccessControlPolicy(context.Background(), channelPolicy)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestGetAccessControlPolicy(t *testing.T) {
|
|
th := SetupConfig(t, func(cfg *model.Config) { cfg.FeatureFlags.AttributeBasedAccessControl = true }).InitBasic(t)
|
|
|
|
samplePolicy := &model.AccessControlPolicy{
|
|
ID: model.NewId(),
|
|
Type: model.AccessControlPolicyTypeChannel,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("GetAccessControlPolicy without license", func(t *testing.T) {
|
|
_, resp, err := th.SystemAdminClient.GetAccessControlPolicy(context.Background(), samplePolicy.ID)
|
|
require.Error(t, err)
|
|
CheckNotImplementedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("GetAccessControlPolicy with regular user", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
// Create and set up the mock
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
mockAccessControlService.On("GetPolicy", mock.AnythingOfType("*request.Context"), samplePolicy.ID).Return(samplePolicy, nil).Times(1)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
_, resp, err := th.Client.GetAccessControlPolicy(context.Background(), samplePolicy.ID)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
// Create and set up the mock
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
mockAccessControlService.On("GetPolicy", mock.AnythingOfType("*request.Context"), samplePolicy.ID).Return(samplePolicy, nil).Times(1)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
_, resp, err := client.GetAccessControlPolicy(context.Background(), samplePolicy.ID)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
}, "GetAccessControlPolicy with system admin")
|
|
}
|
|
|
|
func TestDeleteAccessControlPolicy(t *testing.T) {
|
|
th := SetupConfig(t, func(cfg *model.Config) { cfg.FeatureFlags.AttributeBasedAccessControl = true }).InitBasic(t)
|
|
|
|
samplePolicyID := model.NewId()
|
|
|
|
t.Run("DeleteAccessControlPolicy without license", func(t *testing.T) {
|
|
resp, err := th.SystemAdminClient.DeleteAccessControlPolicy(context.Background(), samplePolicyID)
|
|
require.Error(t, err)
|
|
CheckNotImplementedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("DeleteAccessControlPolicy with regular user", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
|
|
// Mock the GetPolicy call that happens in ValidateAccessControlPolicyPermission
|
|
channelPolicy := &model.AccessControlPolicy{
|
|
ID: samplePolicyID,
|
|
Type: model.AccessControlPolicyTypeChannel,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
mockAccessControlService.On("GetPolicy", mock.AnythingOfType("*request.Context"), samplePolicyID).Return(channelPolicy, nil)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
resp, err := th.Client.DeleteAccessControlPolicy(context.Background(), samplePolicyID)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
mockAccessControlService.On("DeletePolicy", mock.AnythingOfType("*request.Context"), samplePolicyID).Return(nil).Times(1)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
resp, err := client.DeleteAccessControlPolicy(context.Background(), samplePolicyID)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestCheckExpression(t *testing.T) {
|
|
th := SetupConfig(t, func(cfg *model.Config) { cfg.FeatureFlags.AttributeBasedAccessControl = true }).InitBasic(t)
|
|
|
|
t.Run("CheckExpression without license", func(t *testing.T) {
|
|
_, resp, err := th.SystemAdminClient.CheckExpression(context.Background(), "true")
|
|
require.Error(t, err)
|
|
CheckNotImplementedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("CheckExpression with regular user", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
_, resp, err := th.Client.CheckExpression(context.Background(), "true")
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
mockAccessControlService.On("CheckExpression", mock.AnythingOfType("*request.Context"), "true").Return([]model.CELExpressionError{}, nil).Times(1)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
errors, resp, err := client.CheckExpression(context.Background(), "true")
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.Empty(t, errors, "expected no errors")
|
|
}, "CheckExpression with system admin")
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
mockAccessControlService.On("CheckExpression", mock.AnythingOfType("*request.Context"), "true").Return([]model.CELExpressionError{
|
|
{
|
|
Line: 1,
|
|
Column: 1,
|
|
Message: "Syntax error",
|
|
},
|
|
}, nil).Times(1)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
errors, resp, err := client.CheckExpression(context.Background(), "true")
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotEmpty(t, errors, "expected errors")
|
|
}, "CheckExpression with system admin errors returned")
|
|
|
|
t.Run("CheckExpression with channel admin for their channel", func(t *testing.T) {
|
|
// Reload config to pick up the feature flag
|
|
err := th.App.ReloadConfig()
|
|
require.NoError(t, err)
|
|
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
// Add permission to channel admin role
|
|
th.AddPermissionToRole(t, model.PermissionManageChannelAccessRules.Id, model.ChannelAdminRoleId)
|
|
|
|
// Create private channel and make user channel admin
|
|
privateChannel := th.CreatePrivateChannel(t)
|
|
channelAdmin := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, channelAdmin, th.BasicTeam)
|
|
th.AddUserToChannel(t, channelAdmin, privateChannel)
|
|
th.MakeUserChannelAdmin(t, channelAdmin, privateChannel)
|
|
channelAdminClient := th.CreateClient()
|
|
_, _, err = channelAdminClient.Login(context.Background(), channelAdmin.Email, channelAdmin.Password)
|
|
require.NoError(t, err)
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
mockAccessControlService.On("CheckExpression", mock.AnythingOfType("*request.Context"), "true").Return([]model.CELExpressionError{}, nil).Times(1)
|
|
|
|
// Channel admin should be able to check expressions for their channel
|
|
errors, resp, err := channelAdminClient.CheckExpression(context.Background(), "true", privateChannel.Id)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.Empty(t, errors, "expected no errors")
|
|
})
|
|
}
|
|
|
|
func TestTestExpression(t *testing.T) {
|
|
th := SetupConfig(t, func(cfg *model.Config) { cfg.FeatureFlags.AttributeBasedAccessControl = true }).InitBasic(t)
|
|
|
|
t.Run("TestExpression without license", func(t *testing.T) {
|
|
_, resp, err := th.SystemAdminClient.TestExpression(context.Background(), model.QueryExpressionParams{})
|
|
require.Error(t, err)
|
|
CheckNotImplementedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("TestExpression with regular user", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
_, resp, err := th.Client.TestExpression(context.Background(), model.QueryExpressionParams{})
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
mockAccessControlService.On("QueryUsersForExpression", mock.AnythingOfType("*request.Context"), "true", model.SubjectSearchOptions{}).Return([]*model.User{}, int64(0), nil).Times(1)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
usersResp, resp, err := client.TestExpression(context.Background(), model.QueryExpressionParams{
|
|
Expression: "true",
|
|
})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.Empty(t, usersResp.Users, "expected no users")
|
|
require.Equal(t, int64(0), usersResp.Total, "expected count 0 users")
|
|
}, "TestExpression with system admin")
|
|
}
|
|
|
|
func TestSearchAccessControlPolicies(t *testing.T) {
|
|
th := SetupConfig(t, func(cfg *model.Config) { cfg.FeatureFlags.AttributeBasedAccessControl = true }).InitBasic(t)
|
|
|
|
t.Run("SearchAccessControlPolicies without license", func(t *testing.T) {
|
|
_, resp, err := th.SystemAdminClient.SearchAccessControlPolicies(context.Background(), model.AccessControlPolicySearch{})
|
|
require.Error(t, err)
|
|
CheckNotImplementedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("SearchAccessControlPolicies with regular user", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
_, resp, err := th.Client.SearchAccessControlPolicies(context.Background(), model.AccessControlPolicySearch{})
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
mockAccessControlService.On("SearchPolicies", mock.AnythingOfType("*request.Context"), model.AccessControlPolicySearch{
|
|
Term: "engineering",
|
|
}).Return([]*model.AccessControlPolicy{}, int64(0), nil).Times(1)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
policiesResp, resp, err := client.SearchAccessControlPolicies(context.Background(), model.AccessControlPolicySearch{
|
|
Term: "engineering",
|
|
})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.Empty(t, policiesResp.Policies, "expected no policies")
|
|
require.Equal(t, int64(0), policiesResp.Total, "expected count 0 policies")
|
|
}, "SearchAccessControlPolicies with system admin")
|
|
}
|
|
|
|
func TestAssignAccessPolicy(t *testing.T) {
|
|
th := SetupConfig(t, func(cfg *model.Config) { cfg.FeatureFlags.AttributeBasedAccessControl = true }).InitBasic(t)
|
|
|
|
samplePolicy := &model.AccessControlPolicy{
|
|
ID: model.NewId(),
|
|
Type: model.AccessControlPolicyTypeParent,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("AssignAccessPolicy without license", func(t *testing.T) {
|
|
resp, err := th.SystemAdminClient.AssignAccessControlPolicies(context.Background(), model.NewId(), []string{model.NewId()})
|
|
require.Error(t, err)
|
|
CheckNotImplementedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("AssignAccessPolicy with regular user", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
resp, err := th.Client.AssignAccessControlPolicies(context.Background(), model.NewId(), []string{model.NewId()})
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
resourceID := model.NewId()
|
|
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
child := model.AccessControlPolicy{
|
|
ID: resourceID,
|
|
Type: model.AccessControlPolicyTypeChannel,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
}
|
|
|
|
appErr := child.Inherit(samplePolicy)
|
|
require.Nil(t, appErr)
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
mockAccessControlService.On("GetPolicy", mock.AnythingOfType("*request.Context"), samplePolicy.ID).Return(samplePolicy, nil).Times(1)
|
|
mockAccessControlService.On("SavePolicy", mock.AnythingOfType("*request.Context"), mock.AnythingOfType("*model.AccessControlPolicy")).Return(child, nil).Times(1)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
resp, err := client.AssignAccessControlPolicies(context.Background(), samplePolicy.ID, []string{resourceID})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
}, "AssignAccessPolicy with system admin")
|
|
}
|
|
|
|
func TestUnassignAccessPolicy(t *testing.T) {
|
|
th := SetupConfig(t, func(cfg *model.Config) { cfg.FeatureFlags.AttributeBasedAccessControl = true }).InitBasic(t)
|
|
|
|
samplePolicy := &model.AccessControlPolicy{
|
|
ID: model.NewId(),
|
|
Type: model.AccessControlPolicyTypeParent,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("UnassignAccessPolicy without license", func(t *testing.T) {
|
|
resp, err := th.SystemAdminClient.UnassignAccessControlPolicies(context.Background(), samplePolicy.ID, []string{model.NewId()})
|
|
require.Error(t, err)
|
|
CheckNotImplementedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("UnassignAccessPolicy with regular user", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
resp, err := th.Client.UnassignAccessControlPolicies(context.Background(), samplePolicy.ID, []string{model.NewId()})
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
resourceID := model.NewId()
|
|
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
child := &model.AccessControlPolicy{
|
|
ID: resourceID,
|
|
Type: model.AccessControlPolicyTypeChannel,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
}
|
|
|
|
appErr := child.Inherit(samplePolicy)
|
|
require.Nil(t, appErr)
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
mockAccessControlService.On("GetPolicy", mock.AnythingOfType("*request.Context"), samplePolicy.ID).Return(samplePolicy, nil).Times(1)
|
|
mockAccessControlService.On("SearchPolicies", mock.AnythingOfType("*request.Context"), model.AccessControlPolicySearch{
|
|
Type: model.AccessControlPolicyTypeChannel,
|
|
ParentID: samplePolicy.ID,
|
|
}).Return([]*model.AccessControlPolicy{child}, nil).Times(1)
|
|
mockAccessControlService.On("DeletePolicy", mock.AnythingOfType("*request.Context"), child.ID).Return(nil).Times(1)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
resp, err := client.UnassignAccessControlPolicies(context.Background(), samplePolicy.ID, []string{child.ID})
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
}, "UnassignAccessPolicy with system admin")
|
|
}
|
|
|
|
func TestGetChannelsForAccessControlPolicy(t *testing.T) {
|
|
th := SetupConfig(t, func(cfg *model.Config) { cfg.FeatureFlags.AttributeBasedAccessControl = true }).InitBasic(t)
|
|
|
|
samplePolicy := &model.AccessControlPolicy{
|
|
ID: model.NewId(),
|
|
Type: model.AccessControlPolicyTypeParent,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("GetChannelsForAccessControlPolicy without license", func(t *testing.T) {
|
|
_, resp, err := th.SystemAdminClient.GetChannelsForAccessControlPolicy(context.Background(), samplePolicy.ID, "", 1000)
|
|
require.Error(t, err)
|
|
CheckNotImplementedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("GetChannelsForAccessControlPolicy with regular user", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
_, resp, err := th.Client.GetChannelsForAccessControlPolicy(context.Background(), samplePolicy.ID, "", 1000)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
mockAccessControlService.On("GetPolicy", mock.AnythingOfType("*request.Context"), samplePolicy.ID).Return(samplePolicy, nil).Times(1)
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
channelsResp, resp, err := client.GetChannelsForAccessControlPolicy(context.Background(), samplePolicy.ID, "", 1000)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.Empty(t, channelsResp.Channels, "expected no channels")
|
|
require.Equal(t, int64(0), channelsResp.TotalCount, "expected count 0 channels")
|
|
}, "GetChannelsForAccessControlPolicy with system admin")
|
|
}
|
|
|
|
func TestSearchChannelsForAccessControlPolicy(t *testing.T) {
|
|
th := SetupConfig(t, func(cfg *model.Config) { cfg.FeatureFlags.AttributeBasedAccessControl = true }).InitBasic(t)
|
|
|
|
samplePolicy := &model.AccessControlPolicy{
|
|
ID: model.NewId(),
|
|
Type: model.AccessControlPolicyTypeParent,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("SearchChannelsForAccessControlPolicy with regular user", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
_, resp, err := th.Client.SearchChannelsForAccessControlPolicy(context.Background(), samplePolicy.ID, model.ChannelSearch{})
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestSetActiveStatus(t *testing.T) {
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
samplePolicy := &model.AccessControlPolicy{
|
|
ID: th.BasicChannel.Id,
|
|
Type: model.AccessControlPolicyTypeChannel,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
var err error
|
|
samplePolicy, err = th.App.Srv().Store().AccessControlPolicy().Save(th.Context, samplePolicy)
|
|
require.NoError(t, err)
|
|
|
|
// Sample update request
|
|
updateReq := model.AccessControlPolicyActiveUpdateRequest{
|
|
Entries: []model.AccessControlPolicyActiveUpdate{
|
|
{ID: samplePolicy.ID, Active: true},
|
|
},
|
|
}
|
|
|
|
t.Run("SetActiveStatus without license", func(t *testing.T) {
|
|
_, resp, err := th.SystemAdminClient.SetAccessControlPolicyActive(context.Background(), updateReq)
|
|
require.Error(t, err)
|
|
CheckNotImplementedStatus(t, resp)
|
|
})
|
|
|
|
t.Run("SetActiveStatus with regular user", func(t *testing.T) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
// Remove permission from regular user
|
|
_, resp, err := th.Client.SetAccessControlPolicyActive(context.Background(), updateReq)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
|
|
policies, resp, err := client.SetAccessControlPolicyActive(context.Background(), updateReq)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotNil(t, policies, "expected policies in response")
|
|
require.Len(t, policies, 1, "expected one policy in response")
|
|
require.Equal(t, samplePolicy.ID, policies[0].ID, "expected policy ID to match")
|
|
require.True(t, policies[0].Active, "expected policy to be active")
|
|
}, "SetActiveStatus with system admin")
|
|
|
|
t.Run("SetActiveStatus with channel admin for their channel", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
// Add permission to channel admin role
|
|
th.AddPermissionToRole(t, model.PermissionManageChannelAccessRules.Id, model.ChannelAdminRoleId)
|
|
// Create private channel and make user channel admin
|
|
privateChannel := th.CreatePrivateChannel(t)
|
|
channelAdmin := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, channelAdmin, th.BasicTeam)
|
|
th.AddUserToChannel(t, channelAdmin, privateChannel)
|
|
th.MakeUserChannelAdmin(t, channelAdmin, privateChannel)
|
|
|
|
channelPolicy := &model.AccessControlPolicy{
|
|
ID: privateChannel.Id,
|
|
Type: model.AccessControlPolicyTypeChannel,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
var err error
|
|
channelPolicy, err = th.App.Srv().Store().AccessControlPolicy().Save(th.Context, channelPolicy)
|
|
require.NoError(t, err)
|
|
|
|
channelAdminClient := th.CreateClient()
|
|
_, _, err = channelAdminClient.Login(context.Background(), channelAdmin.Email, channelAdmin.Password)
|
|
require.NoError(t, err)
|
|
|
|
// Update request for the channel admin's channel
|
|
channelUpdateReq := model.AccessControlPolicyActiveUpdateRequest{
|
|
Entries: []model.AccessControlPolicyActiveUpdate{
|
|
{ID: privateChannel.Id, Active: true},
|
|
},
|
|
}
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
mockAccessControlService.On("GetPolicy", mock.AnythingOfType("*request.Context"), privateChannel.Id).Return(channelPolicy, nil)
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
|
|
// Channel admin should be able to set active status for their channel
|
|
policies, resp, err := channelAdminClient.SetAccessControlPolicyActive(context.Background(), channelUpdateReq)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
require.NotNil(t, policies, "expected policies in response")
|
|
require.Len(t, policies, 1, "expected one policy in response")
|
|
require.Equal(t, channelPolicy.ID, policies[0].ID, "expected policy ID to match")
|
|
require.True(t, policies[0].Active, "expected policy to be active")
|
|
})
|
|
|
|
t.Run("SetActiveStatus with channel admin for another channel should fail", func(t *testing.T) {
|
|
// This test verifies the security fix: a channel admin cannot modify the active status
|
|
// of a policy for a channel they don't have permissions on, even if they attempt to
|
|
// use a policy ID that matches a channel they control.
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
cfg.AccessControlSettings.EnableAttributeBasedAccessControl = model.NewPointer(true)
|
|
})
|
|
|
|
ok := th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.True(t, ok, "SetLicense should return true")
|
|
|
|
// Add permission to channel admin role
|
|
th.AddPermissionToRole(t, model.PermissionManageChannelAccessRules.Id, model.ChannelAdminRoleId)
|
|
|
|
// Create two private channels
|
|
channelA := th.CreatePrivateChannel(t)
|
|
channelB := th.CreatePrivateChannel(t)
|
|
|
|
// Create a channel admin who only has access to channel A
|
|
channelAdmin := th.CreateUser(t)
|
|
th.LinkUserToTeam(t, channelAdmin, th.BasicTeam)
|
|
th.AddUserToChannel(t, channelAdmin, channelA)
|
|
th.MakeUserChannelAdmin(t, channelAdmin, channelA)
|
|
|
|
// Create a policy for channel B (which the channel admin does NOT have access to)
|
|
channelBPolicy := &model.AccessControlPolicy{
|
|
ID: channelB.Id,
|
|
Type: model.AccessControlPolicyTypeChannel,
|
|
Version: model.AccessControlPolicyVersionV0_2,
|
|
Revision: 1,
|
|
Rules: []model.AccessControlPolicyRule{
|
|
{
|
|
Expression: "user.attributes.team == 'engineering'",
|
|
Actions: []string{"*"},
|
|
},
|
|
},
|
|
}
|
|
_, err := th.App.Srv().Store().AccessControlPolicy().Save(th.Context, channelBPolicy)
|
|
require.NoError(t, err)
|
|
|
|
channelAdminClient := th.CreateClient()
|
|
_, _, err = channelAdminClient.Login(context.Background(), channelAdmin.Email, channelAdmin.Password)
|
|
require.NoError(t, err)
|
|
|
|
mockAccessControlService := &mocks.AccessControlServiceInterface{}
|
|
th.App.Srv().Channels().AccessControl = mockAccessControlService
|
|
mockAccessControlService.On("GetPolicy", mock.AnythingOfType("*request.Context"), channelB.Id).Return(channelBPolicy, nil)
|
|
|
|
// Attempt to update the policy for channel B (which the admin doesn't have access to)
|
|
maliciousUpdateReq := model.AccessControlPolicyActiveUpdateRequest{
|
|
Entries: []model.AccessControlPolicyActiveUpdate{
|
|
{ID: channelB.Id, Active: true},
|
|
},
|
|
}
|
|
|
|
// Channel admin should NOT be able to set active status for another channel's policy
|
|
_, resp, err := channelAdminClient.SetAccessControlPolicyActive(context.Background(), maliciousUpdateReq)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
}
|