Automatic Merge
Этот коммит содержится в:
David Krauser
2026-05-04 03:17:40 -04:00
коммит произвёл GitHub
родитель 073bd3a6b7
Коммит 9408b98025
22 изменённых файлов: 562 добавлений и 69 удалений

Просмотреть файл

@@ -7,12 +7,14 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
"slices"
"strings"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/mattermost/mattermost/server/v8/channels/utils"
@@ -275,13 +277,93 @@ func (a *App) CheckRolesExist(roleNames []string) *model.AppError {
}
func (a *App) sendUpdatedRoleEvent(role *model.Role) *model.AppError {
message := model.NewWebSocketEvent(model.WebsocketEventRoleUpdated, "", "", "", nil, "")
roleJSON, jsonErr := json.Marshal(role)
if jsonErr != nil {
return model.NewAppError("sendUpdatedRoleEvent", "api.marshal_error", nil, "", http.StatusInternalServerError).Wrap(jsonErr)
}
message.Add("role", string(roleJSON))
a.Publish(message)
publishEvent := func(teamID, channelID string) {
message := model.NewWebSocketEvent(model.WebsocketEventRoleUpdated, teamID, channelID, "", nil, "")
message.Add("role", string(roleJSON))
a.Publish(message)
}
// Built-in system roles apply to all users; broadcast globally without a DB lookup.
if role.BuiltIn {
publishEvent("", "")
return nil
}
// Scheme-managed roles: use SchemeId to look up the owning scheme.
if role.SchemeId == nil {
// No owning scheme — treat as global (e.g. custom non-scheme role).
publishEvent("", "")
return nil
}
scheme, err := a.Srv().Store().Scheme().Get(*role.SchemeId)
if err != nil {
a.Log().Error("Failed to look up scheme for role event; skipping broadcast",
mlog.String("role_id", role.Id),
mlog.String("scheme_id", *role.SchemeId),
mlog.Err(err))
return nil
}
const pageSize = 1000
const maxBroadcasts = 100000
switch scheme.Scope {
case model.SchemeScopeTeam:
totalBroadcasts := 0
offset := 0
for {
teams, storeErr := a.Srv().Store().Team().GetTeamsByScheme(scheme.Id, offset, pageSize)
if storeErr != nil {
return model.NewAppError("sendUpdatedRoleEvent", "app.role.send_updated_role_event.app_error", nil, "", http.StatusInternalServerError).Wrap(storeErr)
}
for _, team := range teams {
publishEvent(team.Id, "")
}
totalBroadcasts += len(teams)
if len(teams) < pageSize {
break
}
if totalBroadcasts >= maxBroadcasts {
a.Log().Error("sendUpdatedRoleEvent: hit broadcast limit for team scheme",
mlog.String("scheme_id", scheme.Id),
mlog.Int("totalBroadcasts", totalBroadcasts))
break
}
offset += pageSize
}
case model.SchemeScopeChannel:
totalBroadcasts := 0
offset := 0
for {
channels, storeErr := a.Srv().Store().Channel().GetChannelsByScheme(scheme.Id, offset, pageSize)
if storeErr != nil {
return model.NewAppError("sendUpdatedRoleEvent", "app.role.send_updated_role_event.app_error", nil, "", http.StatusInternalServerError).Wrap(storeErr)
}
for _, channel := range channels {
publishEvent("", channel.Id)
}
totalBroadcasts += len(channels)
if len(channels) < pageSize {
break
}
if totalBroadcasts >= maxBroadcasts {
a.Log().Error("sendUpdatedRoleEvent: hit broadcast limit for channel scheme",
mlog.String("scheme_id", scheme.Id),
mlog.Int("totalBroadcasts", totalBroadcasts))
break
}
offset += pageSize
}
case model.SchemeScopePlaybook, model.SchemeScopeRun:
// Playbook/run schemes don't map to teams or channels; broadcast globally.
publishEvent("", "")
default:
return model.NewAppError("sendUpdatedRoleEvent", "app.role.send_updated_role_event.unknown_scope", nil, fmt.Sprintf("unknown scheme scope: %s", scheme.Scope), http.StatusInternalServerError)
}
return nil
}

Просмотреть файл

@@ -6,6 +6,7 @@ package app
import (
"context"
"encoding/csv"
"errors"
"io"
"os"
"slices"
@@ -13,9 +14,11 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
)
type permissionInheritanceTestData struct {
@@ -261,3 +264,225 @@ func testPermissionInheritance(t *testing.T, testCallback func(t *testing.T, th
// test 24 combinations where the higher-scoped scheme is a TEAM scheme
test(teamScheme.DefaultChannelGuestRole, teamScheme.DefaultChannelUserRole, teamScheme.DefaultChannelAdminRole)
}
func TestSendUpdatedRoleEvent(t *testing.T) {
t.Run("BuiltIn role broadcasts globally without a DB lookup", func(t *testing.T) {
mainHelper.Parallel(t)
th := SetupWithStoreMock(t)
mockStore := th.App.Srv().Store().(*mocks.Store)
mockSchemeStore := mocks.SchemeStore{}
mockStore.On("Scheme").Return(&mockSchemeStore)
role := &model.Role{Name: model.TeamAdminRoleId, BuiltIn: true}
appErr := th.App.sendUpdatedRoleEvent(role)
require.Nil(t, appErr)
mockSchemeStore.AssertNotCalled(t, "Get", mock.Anything)
})
t.Run("Team scheme role calls GetTeamsByScheme and emits per-team events", func(t *testing.T) {
mainHelper.Parallel(t)
th := SetupWithStoreMock(t)
schemeID := model.NewId()
roleName := model.NewId()
scheme := &model.Scheme{Id: schemeID, Scope: model.SchemeScopeTeam}
teams := []*model.Team{{Id: model.NewId()}, {Id: model.NewId()}}
mockStore := th.App.Srv().Store().(*mocks.Store)
mockSchemeStore := mocks.SchemeStore{}
mockTeamStore := mocks.TeamStore{}
mockSchemeStore.On("Get", schemeID).Return(scheme, nil)
mockTeamStore.On("GetTeamsByScheme", schemeID, 0, 1000).Return(teams, nil)
mockStore.On("Scheme").Return(&mockSchemeStore)
mockStore.On("Team").Return(&mockTeamStore)
role := &model.Role{Name: roleName, BuiltIn: false, SchemeId: &schemeID}
appErr := th.App.sendUpdatedRoleEvent(role)
require.Nil(t, appErr)
mockSchemeStore.AssertCalled(t, "Get", schemeID)
mockTeamStore.AssertCalled(t, "GetTeamsByScheme", schemeID, 0, 1000)
})
t.Run("Channel scheme role calls GetChannelsByScheme and emits per-channel events", func(t *testing.T) {
mainHelper.Parallel(t)
th := SetupWithStoreMock(t)
schemeID := model.NewId()
roleName := model.NewId()
scheme := &model.Scheme{Id: schemeID, Scope: model.SchemeScopeChannel}
channels := model.ChannelList{{Id: model.NewId()}}
mockStore := th.App.Srv().Store().(*mocks.Store)
mockSchemeStore := mocks.SchemeStore{}
mockChannelStore := mocks.ChannelStore{}
mockSchemeStore.On("Get", schemeID).Return(scheme, nil)
mockChannelStore.On("GetChannelsByScheme", schemeID, 0, 1000).Return(channels, nil)
mockStore.On("Scheme").Return(&mockSchemeStore)
mockStore.On("Channel").Return(&mockChannelStore)
role := &model.Role{Name: roleName, BuiltIn: false, SchemeId: &schemeID}
appErr := th.App.sendUpdatedRoleEvent(role)
require.Nil(t, appErr)
mockSchemeStore.AssertCalled(t, "Get", schemeID)
mockChannelStore.AssertCalled(t, "GetChannelsByScheme", schemeID, 0, 1000)
})
t.Run("Role not in any scheme broadcasts globally", func(t *testing.T) {
mainHelper.Parallel(t)
th := SetupWithStoreMock(t)
mockStore := th.App.Srv().Store().(*mocks.Store)
mockSchemeStore := mocks.SchemeStore{}
mockTeamStore := mocks.TeamStore{}
mockStore.On("Scheme").Return(&mockSchemeStore)
mockStore.On("Team").Return(&mockTeamStore)
role := &model.Role{Name: model.NewId(), BuiltIn: false, SchemeId: nil}
appErr := th.App.sendUpdatedRoleEvent(role)
require.Nil(t, appErr)
mockSchemeStore.AssertNotCalled(t, "Get", mock.Anything)
mockTeamStore.AssertNotCalled(t, "GetTeamsByScheme", mock.Anything, mock.Anything, mock.Anything)
})
t.Run("Playbook scope falls back to global broadcast without querying teams or channels", func(t *testing.T) {
mainHelper.Parallel(t)
th := SetupWithStoreMock(t)
schemeID := model.NewId()
roleName := model.NewId()
scheme := &model.Scheme{Id: schemeID, Scope: model.SchemeScopePlaybook}
mockStore := th.App.Srv().Store().(*mocks.Store)
mockSchemeStore := mocks.SchemeStore{}
mockTeamStore := mocks.TeamStore{}
mockChannelStore := mocks.ChannelStore{}
mockSchemeStore.On("Get", schemeID).Return(scheme, nil)
mockStore.On("Scheme").Return(&mockSchemeStore)
mockStore.On("Team").Return(&mockTeamStore)
mockStore.On("Channel").Return(&mockChannelStore)
role := &model.Role{Name: roleName, BuiltIn: false, SchemeId: &schemeID}
appErr := th.App.sendUpdatedRoleEvent(role)
require.Nil(t, appErr)
mockTeamStore.AssertNotCalled(t, "GetTeamsByScheme", mock.Anything, mock.Anything, mock.Anything)
mockChannelStore.AssertNotCalled(t, "GetChannelsByScheme", mock.Anything, mock.Anything, mock.Anything)
})
t.Run("Scheme store error is logged and skips broadcast", func(t *testing.T) {
mainHelper.Parallel(t)
th := SetupWithStoreMock(t)
schemeID := model.NewId()
roleName := model.NewId()
mockStore := th.App.Srv().Store().(*mocks.Store)
mockSchemeStore := mocks.SchemeStore{}
mockSchemeStore.On("Get", schemeID).Return(nil, errors.New("db error"))
mockStore.On("Scheme").Return(&mockSchemeStore)
role := &model.Role{Name: roleName, BuiltIn: false, SchemeId: &schemeID}
appErr := th.App.sendUpdatedRoleEvent(role)
require.Nil(t, appErr)
})
t.Run("GetTeamsByScheme store error propagates as AppError", func(t *testing.T) {
mainHelper.Parallel(t)
th := SetupWithStoreMock(t)
schemeID := model.NewId()
roleName := model.NewId()
scheme := &model.Scheme{Id: schemeID, Scope: model.SchemeScopeTeam}
mockStore := th.App.Srv().Store().(*mocks.Store)
mockSchemeStore := mocks.SchemeStore{}
mockTeamStore := mocks.TeamStore{}
mockSchemeStore.On("Get", schemeID).Return(scheme, nil)
mockTeamStore.On("GetTeamsByScheme", schemeID, 0, 1000).Return(nil, errors.New("db error"))
mockStore.On("Scheme").Return(&mockSchemeStore)
mockStore.On("Team").Return(&mockTeamStore)
role := &model.Role{Name: roleName, BuiltIn: false, SchemeId: &schemeID}
appErr := th.App.sendUpdatedRoleEvent(role)
require.NotNil(t, appErr)
})
t.Run("Team scheme paginates across multiple pages", func(t *testing.T) {
mainHelper.Parallel(t)
th := SetupWithStoreMock(t)
schemeID := model.NewId()
scheme := &model.Scheme{Id: schemeID, Scope: model.SchemeScopeTeam}
// Build a full first page (1000 teams) and a partial second page (2 teams).
page1 := make([]*model.Team, 1000)
for i := range page1 {
page1[i] = &model.Team{Id: model.NewId()}
}
page2 := []*model.Team{{Id: model.NewId()}, {Id: model.NewId()}}
mockStore := th.App.Srv().Store().(*mocks.Store)
mockSchemeStore := mocks.SchemeStore{}
mockTeamStore := mocks.TeamStore{}
mockSchemeStore.On("Get", schemeID).Return(scheme, nil)
mockTeamStore.On("GetTeamsByScheme", schemeID, 0, 1000).Return(page1, nil)
mockTeamStore.On("GetTeamsByScheme", schemeID, 1000, 1000).Return(page2, nil)
mockStore.On("Scheme").Return(&mockSchemeStore)
mockStore.On("Team").Return(&mockTeamStore)
role := &model.Role{Name: model.NewId(), BuiltIn: false, SchemeId: &schemeID}
appErr := th.App.sendUpdatedRoleEvent(role)
require.Nil(t, appErr)
mockTeamStore.AssertCalled(t, "GetTeamsByScheme", schemeID, 0, 1000)
mockTeamStore.AssertCalled(t, "GetTeamsByScheme", schemeID, 1000, 1000)
})
t.Run("Channel scheme paginates across multiple pages", func(t *testing.T) {
mainHelper.Parallel(t)
th := SetupWithStoreMock(t)
schemeID := model.NewId()
scheme := &model.Scheme{Id: schemeID, Scope: model.SchemeScopeChannel}
page1 := make(model.ChannelList, 1000)
for i := range page1 {
page1[i] = &model.Channel{Id: model.NewId()}
}
page2 := model.ChannelList{{Id: model.NewId()}, {Id: model.NewId()}, {Id: model.NewId()}}
mockStore := th.App.Srv().Store().(*mocks.Store)
mockSchemeStore := mocks.SchemeStore{}
mockChannelStore := mocks.ChannelStore{}
mockSchemeStore.On("Get", schemeID).Return(scheme, nil)
mockChannelStore.On("GetChannelsByScheme", schemeID, 0, 1000).Return(page1, nil)
mockChannelStore.On("GetChannelsByScheme", schemeID, 1000, 1000).Return(page2, nil)
mockStore.On("Scheme").Return(&mockSchemeStore)
mockStore.On("Channel").Return(&mockChannelStore)
role := &model.Role{Name: model.NewId(), BuiltIn: false, SchemeId: &schemeID}
appErr := th.App.sendUpdatedRoleEvent(role)
require.Nil(t, appErr)
mockChannelStore.AssertCalled(t, "GetChannelsByScheme", schemeID, 0, 1000)
mockChannelStore.AssertCalled(t, "GetChannelsByScheme", schemeID, 1000, 1000)
})
t.Run("GetChannelsByScheme store error propagates as AppError", func(t *testing.T) {
mainHelper.Parallel(t)
th := SetupWithStoreMock(t)
schemeID := model.NewId()
roleName := model.NewId()
scheme := &model.Scheme{Id: schemeID, Scope: model.SchemeScopeChannel}
mockStore := th.App.Srv().Store().(*mocks.Store)
mockSchemeStore := mocks.SchemeStore{}
mockChannelStore := mocks.ChannelStore{}
mockSchemeStore.On("Get", schemeID).Return(scheme, nil)
mockChannelStore.On("GetChannelsByScheme", schemeID, 0, 1000).Return(nil, errors.New("db error"))
mockStore.On("Scheme").Return(&mockSchemeStore)
mockStore.On("Channel").Return(&mockChannelStore)
role := &model.Role{Name: roleName, BuiltIn: false, SchemeId: &schemeID}
appErr := th.App.sendUpdatedRoleEvent(role)
require.NotNil(t, appErr)
})
}

Просмотреть файл

@@ -279,6 +279,12 @@ channels/db/migrations/mysql/000140_add_lastmemberssyncat_to_sharedchannelremote
channels/db/migrations/mysql/000140_add_lastmemberssyncat_to_sharedchannelremotes.up.sql
channels/db/migrations/mysql/000141_add_remoteid_channelid_to_post_acknowledgements.down.sql
channels/db/migrations/mysql/000141_add_remoteid_channelid_to_post_acknowledgements.up.sql
channels/db/migrations/mysql/000142_add_schemeid_to_roles.down.sql
channels/db/migrations/mysql/000142_add_schemeid_to_roles.up.sql
channels/db/migrations/mysql/000143_backfill_roles_schemeid.down.sql
channels/db/migrations/mysql/000143_backfill_roles_schemeid.up.sql
channels/db/migrations/mysql/000144_add_roles_schemeid_index.down.sql
channels/db/migrations/mysql/000144_add_roles_schemeid_index.up.sql
channels/db/migrations/postgres/000001_create_teams.down.sql
channels/db/migrations/postgres/000001_create_teams.up.sql
channels/db/migrations/postgres/000002_create_team_members.down.sql
@@ -559,3 +565,9 @@ channels/db/migrations/postgres/000140_add_lastmemberssyncat_to_sharedchannelrem
channels/db/migrations/postgres/000140_add_lastmemberssyncat_to_sharedchannelremotes.up.sql
channels/db/migrations/postgres/000141_add_remoteid_channelid_to_post_acknowledgements.down.sql
channels/db/migrations/postgres/000141_add_remoteid_channelid_to_post_acknowledgements.up.sql
channels/db/migrations/postgres/000142_add_schemeid_to_roles.down.sql
channels/db/migrations/postgres/000142_add_schemeid_to_roles.up.sql
channels/db/migrations/postgres/000143_backfill_roles_schemeid.down.sql
channels/db/migrations/postgres/000143_backfill_roles_schemeid.up.sql
channels/db/migrations/postgres/000144_add_roles_schemeid_index.down.sql
channels/db/migrations/postgres/000144_add_roles_schemeid_index.up.sql

Просмотреть файл

@@ -0,0 +1,14 @@
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Roles'
AND table_schema = DATABASE()
AND column_name = 'SchemeId'
) > 0,
'ALTER TABLE Roles DROP COLUMN SchemeId;',
'SELECT 1'
));
PREPARE dropColumnIfExists FROM @preparedStatement;
EXECUTE dropColumnIfExists;
DEALLOCATE PREPARE dropColumnIfExists;

Просмотреть файл

@@ -0,0 +1,14 @@
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = 'Roles'
AND table_schema = DATABASE()
AND column_name = 'SchemeId'
) > 0,
'SELECT 1',
'ALTER TABLE Roles ADD COLUMN SchemeId varchar(26);'
));
PREPARE addColumnIfNotExists FROM @preparedStatement;
EXECUTE addColumnIfNotExists;
DEALLOCATE PREPARE addColumnIfNotExists;

Просмотреть файл

@@ -0,0 +1 @@
UPDATE Roles SET SchemeId = NULL;

Просмотреть файл

@@ -0,0 +1,19 @@
UPDATE Roles r
INNER JOIN (
SELECT role_name, MIN(scheme_id) AS scheme_id
FROM (
SELECT Id AS scheme_id, DefaultTeamAdminRole AS role_name FROM Schemes
UNION ALL SELECT Id, DefaultTeamUserRole FROM Schemes
UNION ALL SELECT Id, DefaultTeamGuestRole FROM Schemes
UNION ALL SELECT Id, DefaultChannelAdminRole FROM Schemes
UNION ALL SELECT Id, DefaultChannelUserRole FROM Schemes
UNION ALL SELECT Id, DefaultChannelGuestRole FROM Schemes
UNION ALL SELECT Id, DefaultPlaybookAdminRole FROM Schemes
UNION ALL SELECT Id, DefaultPlaybookMemberRole FROM Schemes
UNION ALL SELECT Id, DefaultRunAdminRole FROM Schemes
UNION ALL SELECT Id, DefaultRunMemberRole FROM Schemes
) expanded
WHERE role_name IS NOT NULL AND role_name <> ''
GROUP BY role_name
) m ON r.Name = m.role_name
SET r.SchemeId = m.scheme_id;

Просмотреть файл

@@ -0,0 +1,14 @@
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
WHERE table_name = 'Roles'
AND table_schema = DATABASE()
AND index_name = 'idx_roles_scheme_id'
) > 0,
'DROP INDEX idx_roles_scheme_id ON Roles;',
'SELECT 1'
));
PREPARE removeIndexIfExists FROM @preparedStatement;
EXECUTE removeIndexIfExists;
DEALLOCATE PREPARE removeIndexIfExists;

Просмотреть файл

@@ -0,0 +1,14 @@
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
WHERE table_name = 'Roles'
AND table_schema = DATABASE()
AND index_name = 'idx_roles_scheme_id'
) > 0,
'SELECT 1',
'CREATE INDEX idx_roles_scheme_id ON Roles(SchemeId);'
));
PREPARE createIndexIfNotExists FROM @preparedStatement;
EXECUTE createIndexIfNotExists;
DEALLOCATE PREPARE createIndexIfNotExists;

Просмотреть файл

@@ -0,0 +1 @@
ALTER TABLE roles DROP COLUMN IF EXISTS schemeid;

Просмотреть файл

@@ -0,0 +1 @@
ALTER TABLE roles ADD COLUMN IF NOT EXISTS schemeid VARCHAR(26);

Просмотреть файл

@@ -0,0 +1 @@
UPDATE roles SET schemeid = NULL;

Просмотреть файл

@@ -0,0 +1,23 @@
UPDATE roles
SET schemeid = match.scheme_id
FROM (
SELECT DISTINCT ON (role_name) id AS scheme_id, role_name
FROM (
SELECT id, unnest(ARRAY[
defaultteamadminrole,
defaultteamuserrole,
defaultteamguestrole,
defaultchanneladminrole,
defaultchanneluserrole,
defaultchannelguestrole,
defaultplaybookadminrole,
defaultplaybookmemberrole,
defaultrunadminrole,
defaultrunmemberrole
]) AS role_name
FROM schemes
) expanded
WHERE role_name IS NOT NULL AND role_name <> ''
ORDER BY role_name, scheme_id
) match
WHERE roles.name = match.role_name;

Просмотреть файл

@@ -0,0 +1,2 @@
-- morph:nontransactional
DROP INDEX CONCURRENTLY IF EXISTS idx_roles_scheme_id;

Просмотреть файл

@@ -0,0 +1,2 @@
-- morph:nontransactional
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_roles_scheme_id ON roles(schemeid);

Просмотреть файл

@@ -33,6 +33,7 @@ type Role struct {
Permissions string
SchemeManaged bool
BuiltIn bool
SchemeId *string
}
type channelRolesPermissions struct {
@@ -66,6 +67,7 @@ func NewRoleFromModel(role *model.Role) *Role {
Permissions: permissions,
SchemeManaged: role.SchemeManaged,
BuiltIn: role.BuiltIn,
SchemeId: role.SchemeId,
}
}
@@ -81,6 +83,7 @@ func (role Role) ToModel() *model.Role {
Permissions: strings.Fields(role.Permissions),
SchemeManaged: role.SchemeManaged,
BuiltIn: role.BuiltIn,
SchemeId: role.SchemeId,
}
}
@@ -90,7 +93,7 @@ func newSqlRoleStore(sqlStore *SqlStore) store.RoleStore {
}
s.tableSelectQuery = s.getQueryBuilder().
Select("Id", "Name", "DisplayName", "Description", "CreateAt", "UpdateAt", "DeleteAt", "Permissions", "SchemeManaged", "BuiltIn").
Select("Id", "Name", "DisplayName", "Description", "CreateAt", "UpdateAt", "DeleteAt", "Permissions", "SchemeManaged", "BuiltIn", "SchemeId").
From("Roles")
return &s
@@ -122,9 +125,10 @@ func (s *SqlRoleStore) Save(role *model.Role) (_ *model.Role, err error) {
dbRole.UpdateAt = model.GetMillis()
res, err := s.GetMaster().NamedExec(`UPDATE Roles
SET UpdateAt=:UpdateAt, DeleteAt=:DeleteAt, CreateAt=:CreateAt, Name=:Name, DisplayName=:DisplayName,
Description=:Description, Permissions=:Permissions, SchemeManaged=:SchemeManaged, BuiltIn=:BuiltIn
WHERE Id=:Id`, &dbRole)
SET UpdateAt=:UpdateAt, DeleteAt=:DeleteAt, CreateAt=:CreateAt, Name=:Name, DisplayName=:DisplayName,
Description=:Description, Permissions=:Permissions, SchemeManaged=:SchemeManaged, BuiltIn=:BuiltIn,
SchemeId=:SchemeId
WHERE Id=:Id`, &dbRole)
if err != nil {
return nil, errors.Wrap(err, "failed to update Role")
@@ -155,9 +159,9 @@ func (s *SqlRoleStore) createRole(role *model.Role, transaction *sqlxTxWrapper)
dbRole.UpdateAt = dbRole.CreateAt
if _, err := transaction.NamedExec(`INSERT INTO Roles
(Id, Name, DisplayName, Description, Permissions, CreateAt, UpdateAt, DeleteAt, SchemeManaged, BuiltIn)
(Id, Name, DisplayName, Description, Permissions, CreateAt, UpdateAt, DeleteAt, SchemeManaged, BuiltIn, SchemeId)
VALUES
(:Id, :Name, :DisplayName, :Description, :Permissions, :CreateAt, :UpdateAt, :DeleteAt, :SchemeManaged, :BuiltIn)`, dbRole); err != nil {
(:Id, :Name, :DisplayName, :Description, :Permissions, :CreateAt, :UpdateAt, :DeleteAt, :SchemeManaged, :BuiltIn, :SchemeId)`, dbRole); err != nil {
return nil, errors.Wrap(err, "failed to save Role")
}
@@ -230,7 +234,7 @@ func (s *SqlRoleStore) GetByNames(names []string) ([]*model.Role, error) {
err = rows.Scan(
&role.Id, &role.Name, &role.DisplayName, &role.Description,
&role.CreateAt, &role.UpdateAt, &role.DeleteAt, &role.Permissions,
&role.SchemeManaged, &role.BuiltIn)
&role.SchemeManaged, &role.BuiltIn, &role.SchemeId)
if err != nil {
return nil, errors.Wrap(err, "failed to scan values")
}
@@ -394,9 +398,10 @@ func (s *SqlRoleStore) AllChannelSchemeRoles() ([]*model.Role, error) {
"Roles.Permissions",
"Roles.SchemeManaged",
"Roles.BuiltIn",
"Roles.SchemeId",
).
From("Schemes").
Join("Roles ON Schemes.DefaultChannelGuestRole = Roles.Name OR Schemes.DefaultChannelUserRole = Roles.Name OR Schemes.DefaultChannelAdminRole = Roles.Name").
From("Roles").
Join("Schemes ON Roles.SchemeId = Schemes.Id").
Where(sq.Eq{"Schemes.Scope": model.SchemeScopeChannel}).
Where(sq.Eq{"Roles.DeleteAt": 0}).
Where(sq.Eq{"Schemes.DeleteAt": 0})
@@ -433,13 +438,14 @@ func (s *SqlRoleStore) ChannelRolesUnderTeamRole(roleName string) ([]*model.Role
"ChannelSchemeRoles.Permissions",
"ChannelSchemeRoles.SchemeManaged",
"ChannelSchemeRoles.BuiltIn",
"ChannelSchemeRoles.SchemeId",
).
From("Roles AS HigherScopedRoles").
Join("Schemes AS HigherScopedSchemes ON (HigherScopedRoles.Name = HigherScopedSchemes.DefaultChannelGuestRole OR HigherScopedRoles.Name = HigherScopedSchemes.DefaultChannelUserRole OR HigherScopedRoles.Name = HigherScopedSchemes.DefaultChannelAdminRole)").
Join("Teams ON Teams.SchemeId = HigherScopedSchemes.Id").
Join("Channels ON Channels.TeamId = Teams.Id").
Join("Schemes AS ChannelSchemes ON Channels.SchemeId = ChannelSchemes.Id").
Join("Roles AS ChannelSchemeRoles ON (ChannelSchemeRoles.Name = ChannelSchemes.DefaultChannelGuestRole OR ChannelSchemeRoles.Name = ChannelSchemes.DefaultChannelUserRole OR ChannelSchemeRoles.Name = ChannelSchemes.DefaultChannelAdminRole)").
Join("Roles AS ChannelSchemeRoles ON ChannelSchemeRoles.SchemeId = ChannelSchemes.Id").
Where(sq.Eq{"HigherScopedSchemes.Scope": model.SchemeScopeTeam}).
Where(sq.Eq{"HigherScopedRoles.Name": roleName}).
Where(sq.Eq{"HigherScopedRoles.DeleteAt": 0}).

Просмотреть файл

@@ -100,6 +100,12 @@ func (s *SqlSchemeStore) Save(scheme *model.Scheme) (_ *model.Scheme, err error)
}
func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxWrapper) (*model.Scheme, error) {
// Generate the scheme ID up front so it can be recorded on each created role.
scheme.Id = model.NewId()
if scheme.Name == "" {
scheme.Name = model.NewId()
}
// Fetch the default system scheme roles to populate default permissions.
defaultRoleNames := []string{
model.TeamAdminRoleId,
@@ -135,6 +141,7 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW
DisplayName: fmt.Sprintf("%s %s", SchemeRoleDisplayNameTeamAdmin, scheme.Name),
Permissions: defaultRoles[model.TeamAdminRoleId].Permissions,
SchemeManaged: true,
SchemeId: &scheme.Id,
}
savedRole, err := s.SqlStore.Role().(*SqlRoleStore).createRole(teamAdminRole, transaction)
@@ -149,6 +156,7 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW
DisplayName: fmt.Sprintf("%s %s", SchemeRoleDisplayNameTeamUser, scheme.Name),
Permissions: defaultRoles[model.TeamUserRoleId].Permissions,
SchemeManaged: true,
SchemeId: &scheme.Id,
}
savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(teamUserRole, transaction)
@@ -163,6 +171,7 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW
DisplayName: fmt.Sprintf("%s %s", SchemeRoleDisplayNameTeamGuest, scheme.Name),
Permissions: defaultRoles[model.TeamGuestRoleId].Permissions,
SchemeManaged: true,
SchemeId: &scheme.Id,
}
savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(teamGuestRole, transaction)
@@ -177,6 +186,7 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW
DisplayName: fmt.Sprintf("%s %s", SchemeRoleDisplayNamePlaybookAdmin, scheme.Name),
Permissions: defaultRoles[model.PlaybookAdminRoleId].Permissions,
SchemeManaged: true,
SchemeId: &scheme.Id,
}
savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(playbookAdminRole, transaction)
if err != nil {
@@ -190,6 +200,7 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW
DisplayName: fmt.Sprintf("%s %s", SchemeRoleDisplayNamePlaybookMember, scheme.Name),
Permissions: defaultRoles[model.PlaybookMemberRoleId].Permissions,
SchemeManaged: true,
SchemeId: &scheme.Id,
}
savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(playbookMemberRole, transaction)
if err != nil {
@@ -203,6 +214,7 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW
DisplayName: fmt.Sprintf("%s %s", SchemeRoleDisplayNameRunAdmin, scheme.Name),
Permissions: defaultRoles[model.RunAdminRoleId].Permissions,
SchemeManaged: true,
SchemeId: &scheme.Id,
}
savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(runAdminRole, transaction)
if err != nil {
@@ -216,6 +228,7 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW
DisplayName: fmt.Sprintf("%s %s", SchemeRoleDisplayNameRunMember, scheme.Name),
Permissions: defaultRoles[model.RunMemberRoleId].Permissions,
SchemeManaged: true,
SchemeId: &scheme.Id,
}
savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(runMemberRole, transaction)
if err != nil {
@@ -231,6 +244,7 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW
DisplayName: fmt.Sprintf("Channel Admin Role for Scheme %s", scheme.Name),
Permissions: defaultRoles[model.ChannelAdminRoleId].Permissions,
SchemeManaged: true,
SchemeId: &scheme.Id,
}
if scheme.Scope == model.SchemeScopeChannel {
@@ -249,6 +263,7 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW
DisplayName: fmt.Sprintf("Channel User Role for Scheme %s", scheme.Name),
Permissions: defaultRoles[model.ChannelUserRoleId].Permissions,
SchemeManaged: true,
SchemeId: &scheme.Id,
}
if scheme.Scope == model.SchemeScopeChannel {
@@ -267,6 +282,7 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW
DisplayName: fmt.Sprintf("Channel Guest Role for Scheme %s", scheme.Name),
Permissions: defaultRoles[model.ChannelGuestRoleId].Permissions,
SchemeManaged: true,
SchemeId: &scheme.Id,
}
if scheme.Scope == model.SchemeScopeChannel {
@@ -280,10 +296,6 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW
scheme.DefaultChannelGuestRole = savedRole.Name
}
scheme.Id = model.NewId()
if scheme.Name == "" {
scheme.Name = model.NewId()
}
scheme.CreateAt = model.GetMillis()
scheme.UpdateAt = scheme.CreateAt
@@ -363,23 +375,11 @@ func (s *SqlSchemeStore) Delete(schemeId string) (*model.Scheme, error) {
s.Channel().ClearCaches()
// Delete the roles belonging to the scheme.
roleNames := []string{scheme.DefaultChannelGuestRole, scheme.DefaultChannelUserRole, scheme.DefaultChannelAdminRole}
if scheme.Scope == model.SchemeScopeTeam {
roleNames = append(roleNames, scheme.DefaultTeamGuestRole, scheme.DefaultTeamUserRole, scheme.DefaultTeamAdminRole)
}
if scheme.Scope == model.SchemeScopePlaybook {
roleNames = append(roleNames, scheme.DefaultPlaybookAdminRole, scheme.DefaultPlaybookMemberRole)
}
if scheme.Scope == model.SchemeScopeRun {
roleNames = append(roleNames, scheme.DefaultRunAdminRole, scheme.DefaultRunMemberRole)
}
time := model.GetMillis()
updateQuery, args, err := s.getQueryBuilder().
Update("Roles").
Where(sq.Eq{"Name": roleNames}).
Where(sq.Eq{"SchemeId": schemeId}).
Set("UpdateAt", time).
Set("DeleteAt", time).
ToSql()
@@ -388,7 +388,7 @@ func (s *SqlSchemeStore) Delete(schemeId string) (*model.Scheme, error) {
}
if _, err = s.GetMaster().Exec(updateQuery, args...); err != nil {
return nil, errors.Wrapf(err, "failed to update Roles with name in (%s)", roleNames)
return nil, errors.Wrapf(err, "failed to update Roles with SchemeId=%s", schemeId)
}
// Delete the scheme itself.

Просмотреть файл

@@ -456,39 +456,45 @@ func testRoleStoreLowerScopedChannelSchemeRoles(t *testing.T, rctx request.CTX,
actualRoles, err := ss.Role().ChannelRolesUnderTeamRole(teamScheme1.DefaultChannelGuestRole)
require.NoError(t, err)
var actualRoleNames []string
roleByName := make(map[string]*model.Role, len(actualRoles))
for _, role := range actualRoles {
actualRoleNames = append(actualRoleNames, role.Name)
roleByName[role.Name] = role
}
require.Contains(t, actualRoleNames, channelScheme1.DefaultChannelGuestRole)
require.NotContains(t, actualRoleNames, channelScheme2.DefaultChannelGuestRole)
require.Contains(t, roleByName, channelScheme1.DefaultChannelGuestRole)
require.NotContains(t, roleByName, channelScheme2.DefaultChannelGuestRole)
require.NotNil(t, roleByName[channelScheme1.DefaultChannelGuestRole].SchemeId)
assert.Equal(t, channelScheme1.Id, *roleByName[channelScheme1.DefaultChannelGuestRole].SchemeId)
})
t.Run("user role for the right team's channels are returned", func(t *testing.T) {
actualRoles, err := ss.Role().ChannelRolesUnderTeamRole(teamScheme1.DefaultChannelUserRole)
require.NoError(t, err)
var actualRoleNames []string
roleByName := make(map[string]*model.Role, len(actualRoles))
for _, role := range actualRoles {
actualRoleNames = append(actualRoleNames, role.Name)
roleByName[role.Name] = role
}
require.Contains(t, actualRoleNames, channelScheme1.DefaultChannelUserRole)
require.NotContains(t, actualRoleNames, channelScheme2.DefaultChannelUserRole)
require.Contains(t, roleByName, channelScheme1.DefaultChannelUserRole)
require.NotContains(t, roleByName, channelScheme2.DefaultChannelUserRole)
require.NotNil(t, roleByName[channelScheme1.DefaultChannelUserRole].SchemeId)
assert.Equal(t, channelScheme1.Id, *roleByName[channelScheme1.DefaultChannelUserRole].SchemeId)
})
t.Run("admin role for the right team's channels are returned", func(t *testing.T) {
actualRoles, err := ss.Role().ChannelRolesUnderTeamRole(teamScheme1.DefaultChannelAdminRole)
require.NoError(t, err)
var actualRoleNames []string
roleByName := make(map[string]*model.Role, len(actualRoles))
for _, role := range actualRoles {
actualRoleNames = append(actualRoleNames, role.Name)
roleByName[role.Name] = role
}
require.Contains(t, actualRoleNames, channelScheme1.DefaultChannelAdminRole)
require.NotContains(t, actualRoleNames, channelScheme2.DefaultChannelAdminRole)
require.Contains(t, roleByName, channelScheme1.DefaultChannelAdminRole)
require.NotContains(t, roleByName, channelScheme2.DefaultChannelAdminRole)
require.NotNil(t, roleByName[channelScheme1.DefaultChannelAdminRole].SchemeId)
assert.Equal(t, channelScheme1.Id, *roleByName[channelScheme1.DefaultChannelAdminRole].SchemeId)
})
})
@@ -497,9 +503,9 @@ func testRoleStoreLowerScopedChannelSchemeRoles(t *testing.T, rctx request.CTX,
actualRoles, err := ss.Role().AllChannelSchemeRoles()
require.NoError(t, err)
var actualRoleNames []string
roleByName := make(map[string]*model.Role, len(actualRoles))
for _, role := range actualRoles {
actualRoleNames = append(actualRoleNames, role.Name)
roleByName[role.Name] = role
}
allRoleNames := []string{
@@ -514,7 +520,27 @@ func testRoleStoreLowerScopedChannelSchemeRoles(t *testing.T, rctx request.CTX,
}
for _, roleName := range allRoleNames {
require.Contains(t, actualRoleNames, roleName)
require.Contains(t, roleByName, roleName)
}
// Roles for channelScheme1 must carry channelScheme1's ID.
for _, roleName := range []string{
channelScheme1.DefaultChannelGuestRole,
channelScheme1.DefaultChannelUserRole,
channelScheme1.DefaultChannelAdminRole,
} {
require.NotNil(t, roleByName[roleName].SchemeId)
assert.Equal(t, channelScheme1.Id, *roleByName[roleName].SchemeId)
}
// Roles for channelScheme2 must carry channelScheme2's ID.
for _, roleName := range []string{
channelScheme2.DefaultChannelGuestRole,
channelScheme2.DefaultChannelUserRole,
channelScheme2.DefaultChannelAdminRole,
} {
require.NotNil(t, roleByName[roleName].SchemeId)
assert.Equal(t, channelScheme2.Id, *roleByName[roleName].SchemeId)
}
})
})

Просмотреть файл

@@ -174,6 +174,28 @@ func testSchemeStoreSave(t *testing.T, rctx request.CTX, ss store.Store) {
assert.Equal(t, role6.Permissions, []string{"read_channel", "read_channel_content", "create_post"})
assert.True(t, role6.SchemeManaged)
role7, err := ss.Role().GetByName(context.Background(), d1.DefaultPlaybookAdminRole)
assert.NoError(t, err)
assert.True(t, role7.SchemeManaged)
role8, err := ss.Role().GetByName(context.Background(), d1.DefaultPlaybookMemberRole)
assert.NoError(t, err)
assert.True(t, role8.SchemeManaged)
role9, err := ss.Role().GetByName(context.Background(), d1.DefaultRunAdminRole)
assert.NoError(t, err)
assert.True(t, role9.SchemeManaged)
role10, err := ss.Role().GetByName(context.Background(), d1.DefaultRunMemberRole)
assert.NoError(t, err)
assert.True(t, role10.SchemeManaged)
// Every role created for a scheme must carry the scheme's ID.
for _, role := range []*model.Role{role1, role2, role3, role4, role5, role6, role7, role8, role9, role10} {
require.NotNil(t, role.SchemeId)
assert.Equal(t, d1.Id, *role.SchemeId)
}
// Change the scheme description and update.
d1.Description = model.NewId()

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@@ -6974,6 +6974,14 @@
"id": "app.role.save.invalid_role.app_error",
"translation": "The role was not valid."
},
{
"id": "app.role.send_updated_role_event.app_error",
"translation": "An error occurred while broadcasting the role update."
},
{
"id": "app.role.send_updated_role_event.unknown_scope",
"translation": "An error occurred while broadcasting the role update: unknown scheme scope."
},
{
"id": "app.save_config.app_error",
"translation": "An error occurred saving the configuration."

Просмотреть файл

@@ -422,6 +422,7 @@ type Role struct {
Permissions []string `json:"permissions"`
SchemeManaged bool `json:"scheme_managed"`
BuiltIn bool `json:"built_in"`
SchemeId *string `json:"scheme_id"`
}
func (r *Role) Auditable() map[string]any {
@@ -436,6 +437,7 @@ func (r *Role) Auditable() map[string]any {
"permissions": r.Permissions,
"scheme_managed": r.SchemeManaged,
"built_in": r.BuiltIn,
"scheme_id": r.SchemeId,
}
}
@@ -456,6 +458,7 @@ func (r *Role) MarshalYAML() (any, error) {
Permissions []string `yaml:"permissions"`
SchemeManaged bool `yaml:"scheme_managed"`
BuiltIn bool `yaml:"built_in"`
SchemeId *string `yaml:"scheme_id"`
}{
Id: r.Id,
Name: r.Name,
@@ -467,6 +470,7 @@ func (r *Role) MarshalYAML() (any, error) {
Permissions: r.Permissions,
SchemeManaged: r.SchemeManaged,
BuiltIn: r.BuiltIn,
SchemeId: r.SchemeId,
}, nil
}
@@ -482,6 +486,7 @@ func (r *Role) UnmarshalYAML(unmarshal func(any) error) error {
Permissions []string `yaml:"permissions"`
SchemeManaged bool `yaml:"scheme_managed"`
BuiltIn bool `yaml:"built_in"`
SchemeId *string `yaml:"scheme_id"`
}{}
err := unmarshal(&out)
@@ -513,6 +518,7 @@ func (r *Role) UnmarshalYAML(unmarshal func(any) error) error {
Permissions: out.Permissions,
SchemeManaged: out.SchemeManaged,
BuiltIn: out.BuiltIn,
SchemeId: out.SchemeId,
}
return nil
}