[MM-61756] Attribute Based Access Control - Phase 1 (#30785)
Attribute Based Access Control - Base * MM-63662 * MM-63919 * MM-63954 * MM-63955 * MM-63425 * MM-63426 * MM-63458 * MM-63459 * MM-63603 * MM-63845 * MM-64146 * MM-64199 * MM-64201 * MM-64233 * MM-64247 * MM-64268 --------- Co-authored-by: Harshil Sharma <harshilsharma63@gmail.com> Co-authored-by: Pablo Andrés Vélez Vidal <pablovv2012@gmail.com> Co-authored-by: abhijit-singh <abhijitsingh0702@gmail.com> Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
4b445cbf16
Коммит
a344b3225b
293
server/channels/app/access_control.go
Обычный файл
293
server/channels/app/access_control.go
Обычный файл
@@ -0,0 +1,293 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
)
|
||||
|
||||
func (a *App) GetChannelsForPolicy(rctx request.CTX, policyID string, cursor model.AccessControlPolicyCursor, limit int) ([]*model.ChannelWithTeamData, int64, *model.AppError) {
|
||||
policy, appErr := a.GetAccessControlPolicy(rctx, policyID)
|
||||
if appErr != nil {
|
||||
return nil, 0, appErr
|
||||
}
|
||||
|
||||
switch policy.Type {
|
||||
case model.AccessControlPolicyTypeParent:
|
||||
policies, total, err := a.Srv().Store().AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{
|
||||
Type: model.AccessControlPolicyTypeChannel,
|
||||
ParentID: policyID,
|
||||
Cursor: cursor,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, model.NewAppError("GetChannelsForPolicy", "app.pap.get_all_access_control_policies.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
channelIDs := make([]string, 0, len(policies))
|
||||
|
||||
for _, p := range policies {
|
||||
channelIDs = append(channelIDs, p.ID)
|
||||
}
|
||||
|
||||
chs, err := a.Srv().Store().Channel().GetChannelsWithTeamDataByIds(channelIDs, true)
|
||||
if err != nil {
|
||||
return nil, 0, model.NewAppError("GetChannelsForPolicy", "app.pap.get_all_access_control_policies.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return chs, total, nil
|
||||
case model.AccessControlPolicyTypeChannel:
|
||||
chs, err := a.Srv().Store().Channel().GetChannelsWithTeamDataByIds([]string{policyID}, true)
|
||||
if err != nil {
|
||||
return nil, 0, model.NewAppError("GetChannelsForPolicy", "app.pap.get_all_access_control_policies.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
total := int64(len(chs))
|
||||
return chs, total, nil
|
||||
default:
|
||||
return nil, 0, model.NewAppError("GetChannelsForPolicy", "app.pap.get_all_access_control_policies.app_error", nil, "Invalid policy type", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) GetAccessControlPolicy(rctx request.CTX, id string) (*model.AccessControlPolicy, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, model.NewAppError("GetPolicy", "app.pap.get_policy.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
policy, appErr := acs.GetPolicy(rctx, id)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func (a *App) CreateOrUpdateAccessControlPolicy(rctx request.CTX, policy *model.AccessControlPolicy) (*model.AccessControlPolicy, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, model.NewAppError("CreateAccessControlPolicy", "app.pap.create_access_control_policy.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
if policy.ID == "" {
|
||||
policy.ID = model.NewId()
|
||||
}
|
||||
|
||||
var appErr *model.AppError
|
||||
policy, appErr = acs.SavePolicy(rctx, policy)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func (a *App) DeleteAccessControlPolicy(rctx request.CTX, id string) *model.AppError {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return model.NewAppError("DeleteAccessControlPolicy", "app.pap.delete_access_control_policy.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
appErr := acs.DeletePolicy(rctx, id)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) CheckExpression(rctx request.CTX, expression string) ([]model.CELExpressionError, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, model.NewAppError("CheckExpression", "app.pap.check_expression.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
errs, appErr := acs.CheckExpression(rctx, expression)
|
||||
if appErr != nil {
|
||||
return nil, model.NewAppError("CheckExpression", "app.pap.check_expression.app_error", nil, appErr.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return errs, nil
|
||||
}
|
||||
|
||||
func (a *App) TestExpression(rctx request.CTX, expression string, opts model.SubjectSearchOptions) ([]*model.User, int64, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, 0, model.NewAppError("TestExpression", "app.pap.check_expression.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
res, count, err := acs.QueryUsersForExpression(rctx, expression, opts)
|
||||
if err != nil {
|
||||
return nil, 0, model.NewAppError("TestExpression", "app.pap.check_expression.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return res, count, nil
|
||||
}
|
||||
|
||||
func (a *App) AssignAccessControlPolicyToChannels(rctx request.CTX, parentID string, channelIDs []string) ([]*model.AccessControlPolicy, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, model.NewAppError("AssignAccessControlPolicyToChannels", "app.pap.assign_access_control_policy_to_channels.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
policy, appErr := a.GetAccessControlPolicy(rctx, parentID)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
if policy.Type != model.AccessControlPolicyTypeParent {
|
||||
return nil, model.NewAppError("AssignAccessControlPolicyToChannels", "app.pap.assign_access_control_policy_to_channels.app_error", nil, "Policy is not of type parent", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
channels, err := a.GetChannels(rctx, channelIDs)
|
||||
if err != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
policies := make([]*model.AccessControlPolicy, 0, len(channelIDs))
|
||||
for _, channel := range channels {
|
||||
if channel.Type != model.ChannelTypePrivate || channel.IsGroupConstrained() {
|
||||
return nil, model.NewAppError("AssignAccessControlPolicyToChannels", "app.pap.assign_access_control_policy_to_channels.app_error", nil, "Channel is not of type private", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if channel.IsShared() {
|
||||
return nil, model.NewAppError("AssignAccessControlPolicyToChannels", "app.pap.assign_access_control_policy_to_channels.app_error", nil, "Channel is shared", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
newPolicy, appErr := policy.Inherit(channel.Id, model.AccessControlPolicyTypeChannel)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
newPolicy, appErr = acs.SavePolicy(rctx, newPolicy)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
policies = append(policies, newPolicy)
|
||||
}
|
||||
|
||||
return policies, nil
|
||||
}
|
||||
|
||||
func (a *App) UnAssignPoliciesFromChannels(rctx request.CTX, policyID string, channelIDs []string) *model.AppError {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return model.NewAppError("UnAssignPoliciesFromChannels", "app.pap.unassign_access_control_policy_from_channels.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
cps, _, err := a.Srv().Store().AccessControlPolicy().SearchPolicies(rctx, model.AccessControlPolicySearch{
|
||||
Type: model.AccessControlPolicyTypeChannel,
|
||||
ParentID: policyID,
|
||||
})
|
||||
if err != nil {
|
||||
return model.NewAppError("UnAssignPoliciesFromChannels", "app.pap.unassign_access_control_policy_from_channels.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
childPolicies := make(map[string]bool)
|
||||
for _, p := range cps {
|
||||
childPolicies[p.ID] = true
|
||||
}
|
||||
|
||||
for _, channelID := range channelIDs {
|
||||
if _, ok := childPolicies[channelID]; !ok {
|
||||
mlog.Warn("Policy is not assigned to the parent policy", mlog.String("channel_id", channelID), mlog.String("parent_policy_id", policyID))
|
||||
continue
|
||||
}
|
||||
|
||||
appErr := acs.DeletePolicy(rctx, channelID)
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) SearchAccessControlPolicies(rctx request.CTX, opts model.AccessControlPolicySearch) ([]*model.AccessControlPolicy, int64, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, 0, model.NewAppError("SearchAccessControlPolicies", "app.pap.search_access_control_policies.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
policies, total, err := a.Srv().Store().AccessControlPolicy().SearchPolicies(rctx, opts)
|
||||
if err != nil {
|
||||
return nil, 0, model.NewAppError("SearchAccessControlPolicies", "app.pap.search_access_control_policies.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
for i, policy := range policies {
|
||||
if policy.Type != model.AccessControlPolicyTypeParent {
|
||||
continue
|
||||
}
|
||||
|
||||
normlizedPolicy, appErr := acs.NormalizePolicy(rctx, policy)
|
||||
if appErr != nil {
|
||||
mlog.Error("Failed to normalize policy", mlog.String("policy_id", policy.ID), mlog.Err(appErr))
|
||||
continue
|
||||
}
|
||||
policies[i] = normlizedPolicy
|
||||
}
|
||||
|
||||
return policies, total, nil
|
||||
}
|
||||
|
||||
func (a *App) GetAccessControlPolicyAttributes(rctx request.CTX, channelID string, action string) (map[string][]string, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, model.NewAppError("GetChannelAccessControlAttributes", "app.pap.get_channel_access_control_attributes.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
attributes, appErr := acs.GetPolicyRuleAttributes(rctx, channelID, action)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return attributes, nil
|
||||
}
|
||||
|
||||
func (a *App) GetAccessControlFieldsAutocomplete(rctx request.CTX, after string, limit int) ([]*model.PropertyField, *model.AppError) {
|
||||
cpaGroupID, err := a.CpaGroupID()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetAccessControlAutoComplete", "app.pap.get_access_control_auto_complete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
fields, err := a.Srv().Store().PropertyField().SearchPropertyFields(model.PropertyFieldSearchOpts{
|
||||
GroupID: cpaGroupID,
|
||||
Cursor: model.PropertyFieldSearchCursor{
|
||||
PropertyFieldID: after,
|
||||
CreateAt: 1,
|
||||
},
|
||||
PerPage: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("GetAccessControlAutoComplete", "app.pap.get_access_control_auto_complete.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func (a *App) UpdateAccessControlPolicyActive(rctx request.CTX, policyID string, active bool) *model.AppError {
|
||||
_, err := a.Srv().Store().AccessControlPolicy().SetActiveStatus(rctx, policyID, active)
|
||||
if err != nil {
|
||||
return model.NewAppError("UpdateAccessControlPolicyActive", "app.pap.update_access_control_policy_active.app_error", nil, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) ExpressionToVisualAST(rctx request.CTX, expression string) (*model.VisualExpression, *model.AppError) {
|
||||
acs := a.Srv().ch.AccessControl
|
||||
if acs == nil {
|
||||
return nil, model.NewAppError("ExpressionToVisualAST", "app.pap.expression_to_visual_ast.app_error", nil, "Policy Administration Point is not initialized", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
visualAST, appErr := acs.ExpressionToVisualAST(rctx, expression)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return visualAST, nil
|
||||
}
|
||||
455
server/channels/app/access_control_test.go
Обычный файл
455
server/channels/app/access_control_test.go
Обычный файл
@@ -0,0 +1,455 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
mocks "github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
|
||||
)
|
||||
|
||||
func TestGetChannelsForPolicy(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
rctx := request.TestContext(t)
|
||||
policyID := "policyID"
|
||||
cursor := model.AccessControlPolicyCursor{}
|
||||
limit := 10
|
||||
|
||||
t.Run("Feature not enabled", func(t *testing.T) {
|
||||
th.App.Srv().ch.AccessControl = nil
|
||||
|
||||
channels, total, err := th.App.GetChannelsForPolicy(rctx, policyID, cursor, limit)
|
||||
require.NotNil(t, err)
|
||||
assert.Nil(t, channels)
|
||||
assert.Equal(t, int64(0), total)
|
||||
})
|
||||
|
||||
t.Run("Invalid policy type", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", mock.AnythingOfType("*request.Context"), policyID).Return(&model.AccessControlPolicy{Type: "invalid"}, nil)
|
||||
|
||||
channels, total, err := th.App.GetChannelsForPolicy(rctx, policyID, cursor, limit)
|
||||
require.NotNil(t, err)
|
||||
require.Nil(t, channels)
|
||||
require.Equal(t, int64(0), total)
|
||||
})
|
||||
|
||||
t.Run("Valid policy type - no channels", func(t *testing.T) {
|
||||
pID := model.NewId()
|
||||
parentPolicy := &model.AccessControlPolicy{
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
ID: pID,
|
||||
Name: "parentPolicy",
|
||||
Revision: 1,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Actions: []string{"*"},
|
||||
Expression: "user.attributes.program == \"non-existent-program\"",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, pID).Return(parentPolicy, nil)
|
||||
|
||||
channels, total, err := th.App.GetChannelsForPolicy(rctx, pID, cursor, limit)
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, channels)
|
||||
require.Equal(t, int64(0), total)
|
||||
})
|
||||
|
||||
t.Run("Valid policy type - with channels", func(t *testing.T) {
|
||||
pID := model.NewId()
|
||||
parentPolicy := &model.AccessControlPolicy{
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
ID: pID,
|
||||
Name: "parentPolicy",
|
||||
Revision: 1,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Actions: []string{"*"},
|
||||
Expression: "user.attributes.program == \"non-existent-program\"",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ch := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
|
||||
childPolicy, appErr := parentPolicy.Inherit(ch.Id, model.AccessControlPolicyTypeChannel)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
var err error
|
||||
childPolicy, err = th.App.Srv().Store().AccessControlPolicy().Save(rctx, childPolicy)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, childPolicy)
|
||||
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, pID).Return(parentPolicy, nil)
|
||||
|
||||
channels, total, appErr := th.App.GetChannelsForPolicy(rctx, pID, cursor, limit)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, channels)
|
||||
require.Equal(t, int64(1), total)
|
||||
assert.Equal(t, ch.Id, channels[0].Id)
|
||||
|
||||
mockAccessControl.On("GetPolicy", rctx, ch.Id).Return(childPolicy, nil)
|
||||
channels, total, appErr = th.App.GetChannelsForPolicy(rctx, ch.Id, cursor, limit)
|
||||
require.Nil(t, appErr)
|
||||
require.NotNil(t, channels)
|
||||
require.Equal(t, int64(1), total)
|
||||
assert.Equal(t, ch.Id, channels[0].Id)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSearchAccessControlPolicies(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
rctx := request.TestContext(t)
|
||||
|
||||
t.Run("Feature not enabled", func(t *testing.T) {
|
||||
policies, total, err := th.App.SearchAccessControlPolicies(rctx, model.AccessControlPolicySearch{})
|
||||
require.NotNil(t, err)
|
||||
require.Empty(t, policies)
|
||||
require.Equal(t, int64(0), total)
|
||||
})
|
||||
|
||||
t.Run("Empty search result", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
|
||||
policies, total, err := th.App.SearchAccessControlPolicies(rctx, model.AccessControlPolicySearch{})
|
||||
require.Nil(t, err)
|
||||
require.Empty(t, policies)
|
||||
require.Equal(t, int64(0), total)
|
||||
})
|
||||
|
||||
t.Run("Single search result", func(t *testing.T) {
|
||||
pID := model.NewId()
|
||||
parentPolicy := &model.AccessControlPolicy{
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
ID: pID,
|
||||
Name: "parentPolicy",
|
||||
Revision: 1,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Actions: []string{"*"},
|
||||
Expression: "user.attributes.program == \"non-existent-program\"",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var err error
|
||||
parentPolicy, err = th.App.Srv().Store().AccessControlPolicy().Save(rctx, parentPolicy)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, parentPolicy)
|
||||
defer func() {
|
||||
dErr := th.App.Srv().Store().AccessControlPolicy().Delete(rctx, parentPolicy.ID)
|
||||
require.NoError(t, dErr)
|
||||
}()
|
||||
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("NormalizePolicy", rctx, parentPolicy).Return(parentPolicy, nil)
|
||||
|
||||
t.Run("With no term", func(t *testing.T) {
|
||||
policies, total, err := th.App.SearchAccessControlPolicies(rctx, model.AccessControlPolicySearch{})
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Equal(t, int64(1), total)
|
||||
require.Equal(t, parentPolicy.ID, policies[0].ID)
|
||||
})
|
||||
|
||||
t.Run("With term", func(t *testing.T) {
|
||||
policies, total, err := th.App.SearchAccessControlPolicies(rctx, model.AccessControlPolicySearch{
|
||||
Term: "parent",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Equal(t, int64(1), total)
|
||||
require.Equal(t, parentPolicy.ID, policies[0].ID)
|
||||
})
|
||||
|
||||
t.Run("With term and no results", func(t *testing.T) {
|
||||
policies, total, err := th.App.SearchAccessControlPolicies(rctx, model.AccessControlPolicySearch{
|
||||
Term: "something else",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.Empty(t, policies)
|
||||
require.Equal(t, int64(0), total)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestAssignAccessControlPolicyToChannels(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
rctx := request.TestContext(t)
|
||||
parentID := model.NewId()
|
||||
|
||||
parentPolicy := &model.AccessControlPolicy{
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
ID: parentID,
|
||||
Name: "parentPolicy",
|
||||
Revision: 1,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{
|
||||
Actions: []string{"*"},
|
||||
Expression: "user.attributes.program == \"non-existent-program\"",
|
||||
},
|
||||
},
|
||||
}
|
||||
var err error
|
||||
parentPolicy, err = th.App.Srv().Store().AccessControlPolicy().Save(rctx, parentPolicy)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, parentPolicy)
|
||||
t.Cleanup(func() {
|
||||
dErr := th.App.Srv().Store().AccessControlPolicy().Delete(rctx, parentPolicy.ID)
|
||||
require.NoError(t, dErr)
|
||||
})
|
||||
|
||||
t.Run("Feature not enabled", func(t *testing.T) {
|
||||
th.App.Srv().ch.AccessControl = nil
|
||||
policies, err := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{})
|
||||
require.NotNil(t, err)
|
||||
assert.Nil(t, policies)
|
||||
assert.Equal(t, "app.pap.assign_access_control_policy_to_channels.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("Error saving policy", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, parentID).Return(parentPolicy, nil)
|
||||
mockAccessControl.On("SavePolicy", rctx, mock.Anything).Return(nil, model.NewAppError("SavePolicy", "error", nil, "save error", http.StatusInternalServerError))
|
||||
|
||||
ch := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
appErr := th.App.PermanentDeleteChannel(rctx, ch)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
policies, err := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{ch.Id})
|
||||
require.NotNil(t, err)
|
||||
require.Empty(t, policies)
|
||||
})
|
||||
|
||||
t.Run("Parent policy not found", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, parentID).Return(nil, model.NewAppError("GetPolicy", "error", nil, "not found", http.StatusNotFound))
|
||||
|
||||
policies, err := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{})
|
||||
require.NotNil(t, err)
|
||||
assert.Nil(t, policies)
|
||||
})
|
||||
|
||||
t.Run("Policy is not of type parent", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, parentID).Return(&model.AccessControlPolicy{Type: model.AccessControlPolicyTypeChannel}, nil)
|
||||
|
||||
policies, err := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{})
|
||||
require.NotNil(t, err)
|
||||
assert.Nil(t, policies)
|
||||
assert.Equal(t, "app.pap.assign_access_control_policy_to_channels.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("Channel is not private", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, parentID).Return(&model.AccessControlPolicy{Type: model.AccessControlPolicyTypeParent}, nil)
|
||||
// Create a public channel
|
||||
publicChannel := th.CreateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
appErr := th.App.PermanentDeleteChannel(rctx, publicChannel)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
policies, err := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{publicChannel.Id})
|
||||
require.NotNil(t, err)
|
||||
assert.Nil(t, policies)
|
||||
assert.Contains(t, err.Error(), "Channel is not of type private")
|
||||
})
|
||||
|
||||
t.Run("Channel is shared", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, parentID).Return(&model.AccessControlPolicy{Type: model.AccessControlPolicyTypeParent}, nil)
|
||||
|
||||
privateChannel := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
appErr := th.App.PermanentDeleteChannel(rctx, privateChannel)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
privateChannel.Shared = model.NewPointer(true)
|
||||
_, err := th.App.Srv().Store().Channel().Update(rctx, privateChannel)
|
||||
require.NoError(t, err)
|
||||
|
||||
policies, appErr := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{privateChannel.Id})
|
||||
require.NotNil(t, appErr)
|
||||
assert.Nil(t, policies)
|
||||
assert.Contains(t, appErr.Error(), "Channel is shared")
|
||||
})
|
||||
|
||||
t.Run("Successful assignment", func(t *testing.T) {
|
||||
ch1 := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
appErr := th.App.PermanentDeleteChannel(rctx, ch1)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
ch2 := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
appErr := th.App.PermanentDeleteChannel(rctx, ch2)
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
childP1, appErr := parentPolicy.Inherit(ch1.Id, model.AccessControlPolicyTypeChannel)
|
||||
require.Nil(t, appErr)
|
||||
childP2, appErr := parentPolicy.Inherit(ch2.Id, model.AccessControlPolicyTypeChannel)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
mockAccessControl.On("GetPolicy", rctx, parentID).Return(parentPolicy, nil)
|
||||
mockAccessControl.On("SavePolicy", rctx, mock.MatchedBy(func(p *model.AccessControlPolicy) bool { return p.ID == ch1.Id })).Return(childP1, nil)
|
||||
mockAccessControl.On("SavePolicy", rctx, mock.MatchedBy(func(p *model.AccessControlPolicy) bool { return p.ID == ch2.Id })).Return(childP2, nil)
|
||||
|
||||
policies, err := th.App.AssignAccessControlPolicyToChannels(rctx, parentID, []string{ch1.Id, ch2.Id})
|
||||
require.Nil(t, err)
|
||||
require.NotNil(t, policies)
|
||||
require.Len(t, policies, 2)
|
||||
assert.ElementsMatch(t, []string{ch1.Id, ch2.Id}, []string{policies[0].ID, policies[1].ID})
|
||||
mockAccessControl.AssertCalled(t, "SavePolicy", rctx, mock.AnythingOfType("*model.AccessControlPolicy"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnAssignPoliciesFromChannels(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
rctx := request.TestContext(t)
|
||||
|
||||
parentPolicy := &model.AccessControlPolicy{
|
||||
ID: model.NewId(),
|
||||
Type: model.AccessControlPolicyTypeParent,
|
||||
Name: "parent-for-unassign-tests",
|
||||
Revision: 1,
|
||||
Version: model.AccessControlPolicyVersionV0_1,
|
||||
Rules: []model.AccessControlPolicyRule{
|
||||
{Actions: []string{"*"}, Expression: "true"},
|
||||
},
|
||||
}
|
||||
var err error
|
||||
parentPolicy, err = th.App.Srv().Store().AccessControlPolicy().Save(rctx, parentPolicy)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, parentPolicy)
|
||||
t.Cleanup(func() {
|
||||
sErr := th.App.Srv().Store().AccessControlPolicy().Delete(rctx, parentPolicy.ID)
|
||||
require.NoError(t, sErr)
|
||||
})
|
||||
|
||||
ch1 := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
sErr := th.App.PermanentDeleteChannel(rctx, ch1)
|
||||
require.Nil(t, sErr)
|
||||
})
|
||||
ch2 := th.CreatePrivateChannel(rctx, th.BasicTeam)
|
||||
t.Cleanup(func() {
|
||||
sErr := th.App.PermanentDeleteChannel(rctx, ch2)
|
||||
require.Nil(t, sErr)
|
||||
})
|
||||
|
||||
childPolicy1, appErrInherit1 := parentPolicy.Inherit(ch1.Id, model.AccessControlPolicyTypeChannel)
|
||||
require.Nil(t, appErrInherit1)
|
||||
childPolicy1, err = th.App.Srv().Store().AccessControlPolicy().Save(rctx, childPolicy1)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, childPolicy1)
|
||||
t.Cleanup(func() {
|
||||
sErr := th.App.Srv().Store().AccessControlPolicy().Delete(rctx, childPolicy1.ID)
|
||||
require.NoError(t, sErr)
|
||||
})
|
||||
|
||||
childPolicy2, appErrInherit2 := parentPolicy.Inherit(ch2.Id, model.AccessControlPolicyTypeChannel)
|
||||
require.Nil(t, appErrInherit2)
|
||||
childPolicy2, err = th.App.Srv().Store().AccessControlPolicy().Save(rctx, childPolicy2)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, childPolicy2)
|
||||
t.Cleanup(func() {
|
||||
sErr := th.App.Srv().Store().AccessControlPolicy().Delete(rctx, childPolicy2.ID)
|
||||
require.NoError(t, sErr)
|
||||
})
|
||||
|
||||
t.Run("Feature not enabled", func(t *testing.T) {
|
||||
th.App.Srv().ch.AccessControl = nil
|
||||
appErr := th.App.UnAssignPoliciesFromChannels(rctx, parentPolicy.ID, []string{ch1.Id, ch2.Id})
|
||||
require.NotNil(t, appErr)
|
||||
assert.Equal(t, "app.pap.unassign_access_control_policy_from_channels.app_error", appErr.Id)
|
||||
})
|
||||
|
||||
t.Run("Error deleting policy from AccessControlService", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
|
||||
expectedErr := model.NewAppError("DeletePolicy", "mock.delete.error", nil, "failed to delete from acs", http.StatusInternalServerError)
|
||||
mockAccessControl.On("DeletePolicy", rctx, ch1.Id).Return(expectedErr).Once()
|
||||
mockAccessControl.On("DeletePolicy", rctx, ch2.Id).Return(nil).Maybe()
|
||||
|
||||
appErr := th.App.UnAssignPoliciesFromChannels(rctx, parentPolicy.ID, []string{ch1.Id, ch2.Id})
|
||||
require.NotNil(t, appErr)
|
||||
assert.Equal(t, expectedErr.Id, appErr.Id)
|
||||
assert.Equal(t, expectedErr.Message, appErr.Message)
|
||||
|
||||
mockAccessControl.AssertCalled(t, "DeletePolicy", rctx, ch1.Id)
|
||||
mockAccessControl.AssertNotCalled(t, "DeletePolicy", rctx, ch2.Id)
|
||||
|
||||
p1, storeErr := th.App.Srv().Store().AccessControlPolicy().Get(rctx, ch1.Id)
|
||||
assert.NoError(t, storeErr)
|
||||
assert.NotNil(t, p1)
|
||||
p2, storeErr := th.App.Srv().Store().AccessControlPolicy().Get(rctx, ch2.Id)
|
||||
assert.NoError(t, storeErr)
|
||||
assert.NotNil(t, p2)
|
||||
})
|
||||
|
||||
t.Run("Channel not actually a child policy", func(t *testing.T) {
|
||||
ch3 := th.CreatePrivateChannel(rctx, th.BasicTeam) // Not a child of parentPolicy
|
||||
t.Cleanup(func() { _ = th.App.PermanentDeleteChannel(rctx, ch3) })
|
||||
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
|
||||
mockAccessControl.On("DeletePolicy", rctx, ch1.Id).Return(nil).Once()
|
||||
mockAccessControl.On("DeletePolicy", rctx, ch2.Id).Return(nil).Once()
|
||||
|
||||
appErr := th.App.UnAssignPoliciesFromChannels(rctx, parentPolicy.ID, []string{ch1.Id, ch2.Id, ch3.Id})
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
|
||||
t.Run("Successful unassignment", func(t *testing.T) {
|
||||
mockAccessControl := &mocks.AccessControlServiceInterface{}
|
||||
th.App.Srv().ch.AccessControl = mockAccessControl
|
||||
|
||||
mockAccessControl.On("DeletePolicy", rctx, ch1.Id).Return(nil).Once()
|
||||
mockAccessControl.On("DeletePolicy", rctx, ch2.Id).Return(nil).Once()
|
||||
|
||||
appErr := th.App.UnAssignPoliciesFromChannels(rctx, parentPolicy.ID, []string{ch1.Id, ch2.Id})
|
||||
require.Nil(t, appErr)
|
||||
})
|
||||
}
|
||||
@@ -635,6 +635,14 @@ func (a *App) GetGroupChannel(c request.CTX, userIDs []string) (*model.Channel,
|
||||
|
||||
// UpdateChannel updates a given channel by its Id. It also publishes the CHANNEL_UPDATED event.
|
||||
func (a *App) UpdateChannel(c request.CTX, channel *model.Channel) (*model.Channel, *model.AppError) {
|
||||
ok, appErr := a.ChannelAccessControlled(c, channel.Id)
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
if ok && channel.Type != model.ChannelTypePrivate {
|
||||
return nil, model.NewAppError("UpdateChannel", "api.channel.update_channel.not_allowed.app_error", nil, "", http.StatusForbidden)
|
||||
}
|
||||
|
||||
_, err := a.Srv().Store().Channel().Update(c, channel)
|
||||
if err != nil {
|
||||
var appErr *model.AppError
|
||||
@@ -1576,6 +1584,40 @@ func (a *App) addUserToChannel(c request.CTX, user *model.User, channel *model.C
|
||||
newMember.SchemeAdmin = userShouldBeAdmin
|
||||
}
|
||||
|
||||
if channel.Type == model.ChannelTypePrivate {
|
||||
if ok, appErr := a.ChannelAccessControlled(c, channel.Id); ok {
|
||||
if acs := a.Srv().Channels().AccessControl; acs != nil {
|
||||
groupID, err := a.CpaGroupID()
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("AddUserToChannel", "api.channel.add_user.to.channel.failed.app_error", nil,
|
||||
fmt.Sprintf("failed to get group: %v, user_id: %s, channel_id: %s", err, user.Id, channel.Id), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
s, err := a.Srv().Store().Attributes().GetSubject(c, user.Id, groupID)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("AddUserToChannel", "api.channel.add_user.to.channel.failed.app_error", nil,
|
||||
fmt.Sprintf("failed to get subject: %v, user_id: %s, channel_id: %s", err, user.Id, channel.Id), http.StatusNotFound)
|
||||
}
|
||||
|
||||
decision, evalErr := acs.AccessEvaluation(c, model.AccessRequest{
|
||||
Subject: *s,
|
||||
Resource: model.Resource{
|
||||
Type: model.AccessControlPolicyTypeChannel,
|
||||
ID: channel.Id,
|
||||
},
|
||||
Action: "join_channel",
|
||||
})
|
||||
if evalErr != nil {
|
||||
return nil, evalErr
|
||||
} else if !decision.Decision {
|
||||
return nil, model.NewAppError("AddUserToChannel", "api.channel.add_user.to.channel.rejected", nil, "", http.StatusForbidden)
|
||||
}
|
||||
}
|
||||
} else if appErr != nil {
|
||||
c.Logger().Error("Error checking access control policy for channel", mlog.Err(appErr))
|
||||
}
|
||||
}
|
||||
|
||||
newMember, nErr = a.Srv().Store().Channel().SaveMember(c, newMember)
|
||||
if nErr != nil {
|
||||
return nil, model.NewAppError("AddUserToChannel", "api.channel.add_user.to.channel.failed.app_error", nil,
|
||||
@@ -1989,13 +2031,15 @@ func (a *App) GetAllChannels(c request.CTX, page, perPage int, opts model.Channe
|
||||
opts.ExcludeChannelNames = a.DefaultChannelNames(c)
|
||||
}
|
||||
storeOpts := store.ChannelSearchOpts{
|
||||
NotAssociatedToGroup: opts.NotAssociatedToGroup,
|
||||
IncludeDeleted: opts.IncludeDeleted,
|
||||
ExcludeChannelNames: opts.ExcludeChannelNames,
|
||||
GroupConstrained: opts.GroupConstrained,
|
||||
ExcludeGroupConstrained: opts.ExcludeGroupConstrained,
|
||||
ExcludePolicyConstrained: opts.ExcludePolicyConstrained,
|
||||
IncludePolicyID: opts.IncludePolicyID,
|
||||
NotAssociatedToGroup: opts.NotAssociatedToGroup,
|
||||
IncludeDeleted: opts.IncludeDeleted,
|
||||
ExcludeChannelNames: opts.ExcludeChannelNames,
|
||||
GroupConstrained: opts.GroupConstrained,
|
||||
ExcludeGroupConstrained: opts.ExcludeGroupConstrained,
|
||||
ExcludePolicyConstrained: opts.ExcludePolicyConstrained,
|
||||
IncludePolicyID: opts.IncludePolicyID,
|
||||
AccessControlPolicyEnforced: opts.AccessControlPolicyEnforced,
|
||||
ExcludeAccessControlPolicyEnforced: opts.ExcludeAccessControlPolicyEnforced,
|
||||
}
|
||||
channels, err := a.Srv().Store().Channel().GetAllChannels(page*perPage, perPage, storeOpts)
|
||||
if err != nil {
|
||||
@@ -2962,22 +3006,25 @@ func (a *App) SearchAllChannels(c request.CTX, term string, opts model.ChannelSe
|
||||
opts.ExcludeChannelNames = a.DefaultChannelNames(c)
|
||||
}
|
||||
storeOpts := store.ChannelSearchOpts{
|
||||
ExcludeChannelNames: opts.ExcludeChannelNames,
|
||||
NotAssociatedToGroup: opts.NotAssociatedToGroup,
|
||||
IncludeDeleted: opts.IncludeDeleted,
|
||||
Deleted: opts.Deleted,
|
||||
TeamIds: opts.TeamIds,
|
||||
GroupConstrained: opts.GroupConstrained,
|
||||
ExcludeGroupConstrained: opts.ExcludeGroupConstrained,
|
||||
PolicyID: opts.PolicyID,
|
||||
IncludePolicyID: opts.IncludePolicyID,
|
||||
IncludeSearchByID: opts.IncludeSearchById,
|
||||
ExcludeRemote: opts.ExcludeRemote,
|
||||
ExcludePolicyConstrained: opts.ExcludePolicyConstrained,
|
||||
Public: opts.Public,
|
||||
Private: opts.Private,
|
||||
Page: opts.Page,
|
||||
PerPage: opts.PerPage,
|
||||
ExcludeChannelNames: opts.ExcludeChannelNames,
|
||||
NotAssociatedToGroup: opts.NotAssociatedToGroup,
|
||||
IncludeDeleted: opts.IncludeDeleted,
|
||||
Deleted: opts.Deleted,
|
||||
TeamIds: opts.TeamIds,
|
||||
GroupConstrained: opts.GroupConstrained,
|
||||
ExcludeGroupConstrained: opts.ExcludeGroupConstrained,
|
||||
PolicyID: opts.PolicyID,
|
||||
IncludePolicyID: opts.IncludePolicyID,
|
||||
IncludeSearchByID: opts.IncludeSearchById,
|
||||
ExcludeRemote: opts.ExcludeRemote,
|
||||
ExcludePolicyConstrained: opts.ExcludePolicyConstrained,
|
||||
Public: opts.Public,
|
||||
Private: opts.Private,
|
||||
Page: opts.Page,
|
||||
PerPage: opts.PerPage,
|
||||
AccessControlPolicyEnforced: opts.AccessControlPolicyEnforced,
|
||||
ExcludeAccessControlPolicyEnforced: opts.ExcludeAccessControlPolicyEnforced,
|
||||
ParentAccessControlPolicyId: opts.ParentAccessControlPolicyId,
|
||||
}
|
||||
|
||||
term = strings.TrimSpace(term)
|
||||
@@ -3815,3 +3862,19 @@ func (s *Server) getDirectChannel(c request.CTX, userID, otherUserID string) (*m
|
||||
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func (a *App) ChannelAccessControlled(c request.CTX, channelID string) (bool, *model.AppError) {
|
||||
if l := a.License(); !model.MinimumEnterpriseAdvancedLicense(l) || !*a.Config().AccessControlSettings.EnableAttributeBasedAccessControl {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
_, err := a.Srv().Store().AccessControlPolicy().Get(c, channelID)
|
||||
var nfErr *store.ErrNotFound
|
||||
if err != nil && !errors.As(err, &nfErr) {
|
||||
return false, model.NewAppError("ChannelIsAccessControlled", "app.channel.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
} else if errors.As(err, &nfErr) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ type Channels struct {
|
||||
Saml einterfaces.SamlInterface
|
||||
Notification einterfaces.NotificationInterface
|
||||
Ldap einterfaces.LdapInterface
|
||||
AccessControl einterfaces.AccessControlServiceInterface
|
||||
|
||||
// These are used to prevent concurrent upload requests
|
||||
// for a given upload session which could cause inconsistencies
|
||||
@@ -132,6 +133,23 @@ func NewChannels(s *Server) (*Channels, error) {
|
||||
}
|
||||
})
|
||||
}
|
||||
if accessControlServiceInterface != nil {
|
||||
app := New(ServerConnector(ch))
|
||||
ch.AccessControl = accessControlServiceInterface(app)
|
||||
|
||||
appErr := ch.AccessControl.Init(request.EmptyContext(s.Log()))
|
||||
if appErr != nil {
|
||||
s.Log().Error("An error occurred while initializing Access Control", mlog.Err(appErr))
|
||||
}
|
||||
|
||||
app.AddLicenseListener(func(newCfg, old *model.License) {
|
||||
if ch.AccessControl != nil {
|
||||
if appErr := ch.AccessControl.Init(request.EmptyContext(s.Log())); appErr != nil {
|
||||
s.Log().Error("An error occurred while initializing Access Control", mlog.Err(appErr))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var imgErr error
|
||||
decoderConcurrency := int(*ch.cfgSvc.Config().FileSettings.MaxImageDecoderConcurrency)
|
||||
|
||||
@@ -98,6 +98,18 @@ func RegisterIPFilteringInterface(f func(*App) einterfaces.IPFilteringInterface)
|
||||
ipFilteringInterface = f
|
||||
}
|
||||
|
||||
var accessControlServiceInterface func(*App) einterfaces.AccessControlServiceInterface
|
||||
|
||||
func RegisterAccessControlServiceInterface(f func(*App) einterfaces.AccessControlServiceInterface) {
|
||||
accessControlServiceInterface = f
|
||||
}
|
||||
|
||||
var jobsAccessControlSyncJobInterface func(*Server) ejobs.AccessControlSyncJobInterface
|
||||
|
||||
func RegisterJobsAccessControlSyncJobInterface(f func(*Server) ejobs.AccessControlSyncJobInterface) {
|
||||
jobsAccessControlSyncJobInterface = f
|
||||
}
|
||||
|
||||
func (s *Server) initEnterprise() {
|
||||
if cloudInterface != nil {
|
||||
s.Cloud = cloudInterface(s)
|
||||
|
||||
@@ -108,6 +108,8 @@ func (a *App) SessionHasPermissionToCreateJob(session model.Session, job *model.
|
||||
model.JobTypeCloud,
|
||||
model.JobTypeExtractContent:
|
||||
return a.SessionHasPermissionTo(session, model.PermissionManageJobs), model.PermissionManageJobs
|
||||
case model.JobTypeAccessControlSync:
|
||||
return a.SessionHasPermissionTo(session, model.PermissionManageSystem), model.PermissionManageSystem
|
||||
}
|
||||
|
||||
return false, nil
|
||||
@@ -142,6 +144,8 @@ func (a *App) SessionHasPermissionToManageJob(session model.Session, job *model.
|
||||
model.JobTypeCloud,
|
||||
model.JobTypeExtractContent:
|
||||
permission = model.PermissionManageJobs
|
||||
case model.JobTypeAccessControlSync:
|
||||
permission = model.PermissionManageSystem
|
||||
}
|
||||
|
||||
if permission == nil {
|
||||
@@ -178,6 +182,8 @@ func (a *App) SessionHasPermissionToReadJob(session model.Session, jobType strin
|
||||
model.JobTypeMobileSessionMetadata,
|
||||
model.JobTypeExtractContent:
|
||||
return a.SessionHasPermissionTo(session, model.PermissionReadJobs), model.PermissionReadJobs
|
||||
case model.JobTypeAccessControlSync:
|
||||
return a.SessionHasPermissionTo(session, model.PermissionManageSystem), model.PermissionManageSystem
|
||||
}
|
||||
|
||||
return false, nil
|
||||
|
||||
@@ -38,8 +38,8 @@ func RegisterMetricsInterface(f func(*PlatformService, string, string) einterfac
|
||||
metricsInterfaceFn = f
|
||||
}
|
||||
|
||||
var pdpInterface func(*PlatformService) einterfaces.PolicyDecisionPointInterface
|
||||
var accessControlServiceInterface func(*PlatformService) einterfaces.AccessControlServiceInterface
|
||||
|
||||
func RegisterPdpInterface(f func(*PlatformService) einterfaces.PolicyDecisionPointInterface) {
|
||||
pdpInterface = f
|
||||
func RegisterAccessControlServiceInterface(f func(*PlatformService) einterfaces.AccessControlServiceInterface) {
|
||||
accessControlServiceInterface = f
|
||||
}
|
||||
|
||||
@@ -477,8 +477,8 @@ func (ps *PlatformService) initEnterprise() {
|
||||
ps.licenseManager = licenseInterface(ps)
|
||||
}
|
||||
|
||||
if pdpInterface != nil {
|
||||
ps.pdpService = pdpInterface(ps)
|
||||
if accessControlServiceInterface != nil {
|
||||
ps.pdpService = accessControlServiceInterface(ps)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1499,6 +1499,11 @@ func (s *Server) initJobs() {
|
||||
s.Jobs.RegisterJobType(model.JobTypeLdapSync, builder.MakeWorker(), builder.MakeScheduler())
|
||||
}
|
||||
|
||||
if jobsAccessControlSyncJobInterface != nil {
|
||||
builder := jobsAccessControlSyncJobInterface(s)
|
||||
s.Jobs.RegisterJobType(model.JobTypeAccessControlSync, builder.MakeWorker(), builder.MakeScheduler())
|
||||
}
|
||||
|
||||
s.Jobs.RegisterJobType(
|
||||
model.JobTypeBlevePostIndexing,
|
||||
indexer.MakeWorker(s.Jobs, s.platform.SearchEngine.BleveEngine.(*bleveengine.BleveEngine)),
|
||||
|
||||
@@ -2090,6 +2090,26 @@ func (a *App) SearchUsersInChannel(channelID string, term string, options *model
|
||||
|
||||
func (a *App) SearchUsersNotInChannel(teamID string, channelID string, term string, options *model.UserSearchOptions) ([]*model.User, *model.AppError) {
|
||||
term = strings.TrimSpace(term)
|
||||
|
||||
ctx := request.EmptyContext(a.Log())
|
||||
if ok, err := a.ChannelAccessControlled(ctx, channelID); err != nil {
|
||||
return nil, err
|
||||
} else if ok {
|
||||
acs := a.Srv().Channels().AccessControl
|
||||
if acs != nil {
|
||||
users, _, appErr := acs.QueryUsersForResource(ctx, channelID, "*", model.SubjectSearchOptions{
|
||||
Term: term,
|
||||
TeamID: teamID,
|
||||
Limit: options.Limit,
|
||||
})
|
||||
if appErr != nil {
|
||||
return nil, appErr
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
}
|
||||
|
||||
users, err := a.Srv().Store().User().SearchNotInChannel(teamID, channelID, term, options)
|
||||
if err != nil {
|
||||
return nil, model.NewAppError("SearchUsersNotInChannel", "app.user.search.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
|
||||
Ссылка в новой задаче
Block a user