mirror of
https://github.com/mattermost/mattermost.git
synced 2026-06-11 20:07:34 +00:00
Add manage_own_agent and manage_others_agent permissions (#35924)
* Add PermissionCreateAgent server-side permission definition
Define PermissionCreateAgent in the model layer with system scope,
add to SystemScopedPermissionsMinusSysconsole (feeds AllPermissions),
grant to system_user in MakeDefaultRoles(), and register a permissions
migration for existing installations (system_admin + system_user).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add exhaustive tests for PermissionCreateAgent permission
Model tests: verify create_agent is in AllPermissions, has system scope,
correct i18n fields, present in system_admin and system_user default roles,
and absent from system_guest.
Migration test: verify getAddCreateAgentPermissionMigration adds create_agent
to both system_admin and system_user, and is idempotent on re-run.
Also register the migration key in testlib mock store so server initialization
skips it during test setup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add webapp permission constants and i18n for create_agent
Add CREATE_AGENT constant to permissions.ts, display strings with
defineMessages in permissions.tsx, and i18n entries in en.json so the
permission appears in System Console Permission Schemes UI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Clean up tests and minor fixups for create_agent permission
Consolidate role_test.go into table-driven tests, remove redundant comments
in permissions_migrations_test.go, add .planning/ to .gitignore, and
refresh webapp/package-lock.json.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Split create_agent into manage_own_agent and manage_others_agent
Replace PermissionCreateAgent with system-scoped own/others permissions,
update migration and defaults, and wire System Scheme UI for integrations.
Made-with: Cursor
* fixes
* Stabilize autotranslation E2E by pinning mock source language
Set LibreTranslate mock to English before the pre-enable post and Spanish
before the post-enable message so parallel tests cannot leave the mock in
a state where the new message is not translated.
Made-with: Cursor
* Revert package-lock, add more chnages
* Revert "Revert package-lock, add more chnages"
This reverts commit 7f6752c2e0.
* Drop unrelated autotranslation E2E tweak; restore package-lock
The Playwright autotranslation change was not caused by MM-65671. Revert
that test edit and restore webapp/package-lock.json after an accidental
revert of the prior package-lock update.
Made-with: Cursor
* Put package-lock back again
* fixes
* Fix migration tests for manage_own_agent on system_user role
Made-with: Cursor
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
Mattermost Build
parent
62d0ab633f
commit
d4d65c8cfb
@@ -160,6 +160,7 @@ docker-compose.override.yaml
|
||||
.notice-work/
|
||||
.aider*
|
||||
.env
|
||||
.planning/
|
||||
|
||||
**/CLAUDE.local.md
|
||||
**/CLAUDE.md
|
||||
|
||||
@@ -210,6 +210,7 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
|
||||
model.PermissionDeleteCustomGroup.Id,
|
||||
model.PermissionRestoreCustomGroup.Id,
|
||||
model.PermissionManageCustomGroupMembers.Id,
|
||||
model.PermissionManageOwnAgent.Id,
|
||||
},
|
||||
"system_post_all": {
|
||||
model.PermissionCreatePost.Id,
|
||||
@@ -278,6 +279,7 @@ func TestDoEmojisPermissionsMigration(t *testing.T) {
|
||||
model.PermissionCreateEmojis.Id,
|
||||
model.PermissionDeleteEmojis.Id,
|
||||
model.PermissionViewMembers.Id,
|
||||
model.PermissionManageOwnAgent.Id,
|
||||
}
|
||||
assert.ElementsMatch(t, expected3, role3.Permissions, fmt.Sprintf("'%v' did not have expected permissions", model.SystemUserRoleId))
|
||||
|
||||
|
||||
@@ -1284,6 +1284,24 @@ func (a *App) getRestoreManageOAuthPermissionMigration() (permissionsMap, error)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *App) getAddManageAgentPermissionsMigration() (permissionsMap, error) {
|
||||
return permissionsMap{
|
||||
permissionTransformation{
|
||||
On: isExactRole(model.SystemAdminRoleId),
|
||||
Add: []string{
|
||||
model.PermissionManageOwnAgent.Id,
|
||||
model.PermissionManageOthersAgent.Id,
|
||||
},
|
||||
},
|
||||
permissionTransformation{
|
||||
On: isExactRole(model.SystemUserRoleId),
|
||||
Add: []string{
|
||||
model.PermissionManageOwnAgent.Id,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DoPermissionsMigrations execute all the permissions migrations need by the current version.
|
||||
func (a *App) DoPermissionsMigrations() error {
|
||||
return a.Srv().doPermissionsMigrations()
|
||||
@@ -1343,6 +1361,7 @@ func (s *Server) doPermissionsMigrations() error {
|
||||
{Key: model.MigrationKeyAddChannelAutoTranslationPermissions, Migration: a.getAddChannelAutoTranslationPermissionMigration},
|
||||
{Key: model.MigrationKeyAddSharedChannelManagerPermissions, Migration: a.getAddSharedChannelManagerPermissionsMigration},
|
||||
{Key: model.MigrationKeyRestoreManageOAuthPermission, Migration: a.getRestoreManageOAuthPermissionMigration},
|
||||
{Key: model.MigrationKeyAddManageAgentPermissions, Migration: a.getAddManageAgentPermissionsMigration},
|
||||
}
|
||||
|
||||
roles, err := s.Store().Role().GetAll()
|
||||
|
||||
@@ -65,6 +65,60 @@ func TestRestoreManageOAuthPermissionMigration(t *testing.T) {
|
||||
systemStore.AssertNumberOfCalls(t, "SaveOrUpdate", 1)
|
||||
}
|
||||
|
||||
func TestAddManageAgentPermissionsMigration(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
|
||||
th := SetupWithStoreMock(t)
|
||||
|
||||
migrationMap, err := th.App.getAddManageAgentPermissionsMigration()
|
||||
require.NoError(t, err)
|
||||
|
||||
systemAdminRole := &model.Role{
|
||||
Name: model.SystemAdminRoleId,
|
||||
Permissions: []string{model.PermissionManageSystem.Id},
|
||||
}
|
||||
systemUserRole := &model.Role{
|
||||
Name: model.SystemUserRoleId,
|
||||
Permissions: []string{model.PermissionCreateDirectChannel.Id},
|
||||
}
|
||||
roles := []*model.Role{systemAdminRole, systemUserRole}
|
||||
|
||||
mockStore := th.App.Srv().Store().(*mocks.Store)
|
||||
roleStore := mocks.RoleStore{}
|
||||
systemStore := mocks.SystemStore{}
|
||||
|
||||
mockStore.On("Role").Return(&roleStore)
|
||||
mockStore.On("System").Return(&systemStore)
|
||||
|
||||
systemStore.On("GetByName", model.MigrationKeyAddManageAgentPermissions).
|
||||
Return(nil, model.NewAppError("test", "missing", nil, "", 404)).Once()
|
||||
systemStore.On("GetByName", model.MigrationKeyAddManageAgentPermissions).
|
||||
Return(&model.System{Name: model.MigrationKeyAddManageAgentPermissions, Value: "true"}, nil).Once()
|
||||
systemStore.On("SaveOrUpdate", mock.MatchedBy(func(system *model.System) bool {
|
||||
return system.Name == model.MigrationKeyAddManageAgentPermissions && system.Value == "true"
|
||||
})).Return(nil).Once()
|
||||
|
||||
roleStore.On("Save", mock.AnythingOfType("*model.Role")).
|
||||
Return(func(role *model.Role) *model.Role { return role }, nil).Twice()
|
||||
|
||||
appErr := th.App.Srv().doPermissionsMigration(model.MigrationKeyAddManageAgentPermissions, migrationMap, roles)
|
||||
require.Nil(t, appErr)
|
||||
assert.Contains(t, systemAdminRole.Permissions, model.PermissionManageOwnAgent.Id)
|
||||
assert.Contains(t, systemAdminRole.Permissions, model.PermissionManageOthersAgent.Id)
|
||||
assert.Contains(t, systemUserRole.Permissions, model.PermissionManageOwnAgent.Id)
|
||||
assert.NotContains(t, systemUserRole.Permissions, model.PermissionManageOthersAgent.Id)
|
||||
assert.Len(t, systemAdminRole.Permissions, 3)
|
||||
assert.Len(t, systemUserRole.Permissions, 2)
|
||||
|
||||
appErr = th.App.Srv().doPermissionsMigration(model.MigrationKeyAddManageAgentPermissions, migrationMap, roles)
|
||||
require.Nil(t, appErr)
|
||||
assert.Len(t, systemAdminRole.Permissions, 3, "system_admin should still have 3 permissions after idempotent run")
|
||||
assert.Len(t, systemUserRole.Permissions, 2, "system_user should still have 2 permissions after idempotent run")
|
||||
|
||||
roleStore.AssertNumberOfCalls(t, "Save", 2)
|
||||
systemStore.AssertNumberOfCalls(t, "SaveOrUpdate", 1)
|
||||
}
|
||||
|
||||
func TestApplyPermissionsMap(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
tt := []struct {
|
||||
|
||||
@@ -96,6 +96,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store {
|
||||
systemStore.On("GetByName", model.MigrationKeyAddChannelAutoTranslationPermissions).Return(&model.System{Name: model.MigrationKeyAddChannelAutoTranslationPermissions, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", model.MigrationKeyRestoreManageOAuthPermission).Return(&model.System{Name: model.MigrationKeyRestoreManageOAuthPermission, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", model.MigrationKeyAccessControlPolicyV0_3).Return(&model.System{Name: model.MigrationKeyAccessControlPolicyV0_3, Value: "true"}, nil)
|
||||
systemStore.On("GetByName", model.MigrationKeyAddManageAgentPermissions).Return(&model.System{Name: model.MigrationKeyAddManageAgentPermissions, Value: "true"}, nil)
|
||||
|
||||
systemStore.On("InsertIfExists", mock.AnythingOfType("*model.System")).Return(&model.System{}, nil).Once()
|
||||
systemStore.On("Save", mock.AnythingOfType("*model.System")).Return(nil)
|
||||
|
||||
@@ -61,4 +61,5 @@ const (
|
||||
MigrationKeyAddSharedChannelManagerPermissions = "system_shared_channel_manager_permissions"
|
||||
MigrationKeyRestoreManageOAuthPermission = "restore_manage_oauth_permission"
|
||||
MigrationKeyAccessControlPolicyV0_3 = "access_control_policy_v0_3_migration"
|
||||
MigrationKeyAddManageAgentPermissions = "add_manage_agent_permissions"
|
||||
)
|
||||
|
||||
@@ -416,6 +416,8 @@ var SysconsoleReadPermissions []*Permission
|
||||
var SysconsoleWritePermissions []*Permission
|
||||
|
||||
var PermissionManageOutgoingOAuthConnections *Permission
|
||||
var PermissionManageOwnAgent *Permission
|
||||
var PermissionManageOthersAgent *Permission
|
||||
var ModeratedBookmarkPermissions []*Permission
|
||||
|
||||
func initializePermissions() {
|
||||
@@ -2328,6 +2330,19 @@ func initializePermissions() {
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
|
||||
PermissionManageOwnAgent = &Permission{
|
||||
"manage_own_agent",
|
||||
"authentication.permissions.manage_own_agent.name",
|
||||
"authentication.permissions.manage_own_agent.description",
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
PermissionManageOthersAgent = &Permission{
|
||||
"manage_others_agent",
|
||||
"authentication.permissions.manage_others_agent.name",
|
||||
"authentication.permissions.manage_others_agent.description",
|
||||
PermissionScopeSystem,
|
||||
}
|
||||
|
||||
SysconsoleReadPermissions = []*Permission{
|
||||
PermissionSysconsoleReadAboutEditionAndLicense,
|
||||
PermissionSysconsoleReadBilling,
|
||||
@@ -2524,6 +2539,8 @@ func initializePermissions() {
|
||||
PermissionManageLicenseInformation,
|
||||
PermissionCreateCustomGroup,
|
||||
PermissionManageOutgoingOAuthConnections,
|
||||
PermissionManageOwnAgent,
|
||||
PermissionManageOthersAgent,
|
||||
}
|
||||
|
||||
TeamScopedPermissions := []*Permission{
|
||||
|
||||
@@ -1108,6 +1108,7 @@ func MakeDefaultRoles() map[string]*Role {
|
||||
PermissionDeleteCustomGroup.Id,
|
||||
PermissionRestoreCustomGroup.Id,
|
||||
PermissionManageCustomGroupMembers.Id,
|
||||
PermissionManageOwnAgent.Id,
|
||||
},
|
||||
SchemeManaged: true,
|
||||
BuiltIn: true,
|
||||
|
||||
@@ -341,3 +341,46 @@ func TestMakeDefaultRolesContainsNewManagerRoles(t *testing.T) {
|
||||
}), "manage_oauth should not remain deprecated")
|
||||
})
|
||||
}
|
||||
|
||||
func TestManageAgentPermissionsDefinition(t *testing.T) {
|
||||
assert.Equal(t, "manage_own_agent", PermissionManageOwnAgent.Id)
|
||||
assert.Equal(t, "authentication.permissions.manage_own_agent.name", PermissionManageOwnAgent.Name)
|
||||
assert.Equal(t, "authentication.permissions.manage_own_agent.description", PermissionManageOwnAgent.Description)
|
||||
assert.Equal(t, PermissionScopeSystem, PermissionManageOwnAgent.Scope,
|
||||
"manage_own_agent should have system scope")
|
||||
assert.True(t, slices.ContainsFunc(AllPermissions, func(p *Permission) bool {
|
||||
return p.Id == PermissionManageOwnAgent.Id
|
||||
}), "manage_own_agent should be in AllPermissions")
|
||||
|
||||
assert.Equal(t, "manage_others_agent", PermissionManageOthersAgent.Id)
|
||||
assert.Equal(t, "authentication.permissions.manage_others_agent.name", PermissionManageOthersAgent.Name)
|
||||
assert.Equal(t, "authentication.permissions.manage_others_agent.description", PermissionManageOthersAgent.Description)
|
||||
assert.Equal(t, PermissionScopeSystem, PermissionManageOthersAgent.Scope,
|
||||
"manage_others_agent should have system scope")
|
||||
assert.True(t, slices.ContainsFunc(AllPermissions, func(p *Permission) bool {
|
||||
return p.Id == PermissionManageOthersAgent.Id
|
||||
}), "manage_others_agent should be in AllPermissions")
|
||||
}
|
||||
|
||||
func TestManageAgentPermissionsDefaultRoles(t *testing.T) {
|
||||
roles := MakeDefaultRoles()
|
||||
|
||||
for _, tc := range []struct {
|
||||
roleId string
|
||||
expectOwn bool
|
||||
expectOthers bool
|
||||
}{
|
||||
{SystemAdminRoleId, true, true},
|
||||
{SystemUserRoleId, true, false},
|
||||
{SystemGuestRoleId, false, false},
|
||||
} {
|
||||
t.Run(tc.roleId, func(t *testing.T) {
|
||||
role, ok := roles[tc.roleId]
|
||||
require.True(t, ok, "%s role should exist", tc.roleId)
|
||||
assert.Equal(t, tc.expectOwn, slices.Contains(role.Permissions, PermissionManageOwnAgent.Id),
|
||||
"%s manage_own_agent permission presence", tc.roleId)
|
||||
assert.Equal(t, tc.expectOthers, slices.Contains(role.Permissions, PermissionManageOthersAgent.Id),
|
||||
"%s manage_others_agent permission presence", tc.roleId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -238,6 +238,16 @@ export default class PermissionsTree extends React.PureComponent<Props, State> {
|
||||
integrationsGroup.permissions.push(outgoingWebhookGroup);
|
||||
}
|
||||
}
|
||||
const manageAgentsGroup = {
|
||||
id: 'manage_agents_group',
|
||||
permissions: [
|
||||
Permissions.MANAGE_OWN_AGENT,
|
||||
Permissions.MANAGE_OTHERS_AGENT,
|
||||
],
|
||||
};
|
||||
if (!integrationsGroup.permissions.some((p: any) => p.id === 'manage_agents_group')) {
|
||||
integrationsGroup.permissions.push(manageAgentsGroup);
|
||||
}
|
||||
if (config.EnableOAuthServiceProvider === 'true' && !integrationsGroup.permissions.includes(Permissions.MANAGE_OAUTH)) {
|
||||
integrationsGroup.permissions.push(Permissions.MANAGE_OAUTH);
|
||||
}
|
||||
|
||||
+10
@@ -125,6 +125,16 @@ export const groupRolesStrings: Record<string, Record<string, MessageDescriptor>
|
||||
defaultMessage: 'Manage own and others\' outgoing webhooks.',
|
||||
},
|
||||
}),
|
||||
manage_agents_group: defineMessages({
|
||||
name: {
|
||||
id: 'admin.permissions.group.manage_agents.name',
|
||||
defaultMessage: 'Manage AI Agents',
|
||||
},
|
||||
description: {
|
||||
id: 'admin.permissions.group.manage_agents.description',
|
||||
defaultMessage: 'Manage own and others\' AI agents.',
|
||||
},
|
||||
}),
|
||||
manage_slash_commands_group: defineMessages({
|
||||
name: {
|
||||
id: 'admin.permissions.group.manage_slash_commands.name',
|
||||
|
||||
+20
@@ -35,6 +35,26 @@ export const permissionRolesStrings: Record<string, Record<string, MessageDescri
|
||||
defaultMessage: 'Convert private channels to public',
|
||||
},
|
||||
}),
|
||||
manage_own_agent: defineMessages({
|
||||
name: {
|
||||
id: 'admin.permissions.permission.manage_own_agent.name',
|
||||
defaultMessage: 'Manage own AI agents',
|
||||
},
|
||||
description: {
|
||||
id: 'admin.permissions.permission.manage_own_agent.description',
|
||||
defaultMessage: 'Create and manage your own AI agents.',
|
||||
},
|
||||
}),
|
||||
manage_others_agent: defineMessages({
|
||||
name: {
|
||||
id: 'admin.permissions.permission.manage_others_agent.name',
|
||||
defaultMessage: 'Manage others\' AI agents',
|
||||
},
|
||||
description: {
|
||||
id: 'admin.permissions.permission.manage_others_agent.description',
|
||||
defaultMessage: 'Create and manage AI agents for other users.',
|
||||
},
|
||||
}),
|
||||
create_direct_channel: defineMessages({
|
||||
name: {
|
||||
id: 'admin.permissions.permission.create_direct_channel.name',
|
||||
|
||||
@@ -1923,6 +1923,8 @@
|
||||
"admin.permissions.group.guest_use_group_mentions.name": "Group Mentions",
|
||||
"admin.permissions.group.integrations.description": "Manage OAuth 2.0, slash commands, webhooks and emoji.",
|
||||
"admin.permissions.group.integrations.name": "Integrations & Customizations",
|
||||
"admin.permissions.group.manage_agents.description": "Manage own and others' AI agents.",
|
||||
"admin.permissions.group.manage_agents.name": "Manage AI Agents",
|
||||
"admin.permissions.group.manage_incoming_webhooks.description": "Manage own and others' incoming webhooks.",
|
||||
"admin.permissions.group.manage_incoming_webhooks.name": "Manage Incoming Webhooks",
|
||||
"admin.permissions.group.manage_oauth.description": "Manage own and others' OAuth 2.0 applications.",
|
||||
@@ -2033,6 +2035,8 @@
|
||||
"admin.permissions.permission.manage_jobs.name": "Manage jobs",
|
||||
"admin.permissions.permission.manage_oauth.description": "Create, edit and delete OAuth 2.0 application tokens.",
|
||||
"admin.permissions.permission.manage_oauth.name": "Manage OAuth Applications",
|
||||
"admin.permissions.permission.manage_others_agent.description": "Create and manage AI agents for other users.",
|
||||
"admin.permissions.permission.manage_others_agent.name": "Manage others' AI agents",
|
||||
"admin.permissions.permission.manage_others_incoming_webhooks.description": "Create, edit, and delete incoming webhooks owned by other users.",
|
||||
"admin.permissions.permission.manage_others_incoming_webhooks.name": "Manage Others'",
|
||||
"admin.permissions.permission.manage_others_outgoing_webhooks.description": "Create, edit, and delete outgoing webhooks owned by other users.",
|
||||
@@ -2043,6 +2047,8 @@
|
||||
"admin.permissions.permission.manage_outgoing_oauth_connections.name": "Manage Outgoing OAuth Credentials",
|
||||
"admin.permissions.permission.manage_outgoing_webhooks.description": "Create, edit, and delete outgoing webhooks owned by other users.",
|
||||
"admin.permissions.permission.manage_outgoing_webhooks.name": "Manage Others'",
|
||||
"admin.permissions.permission.manage_own_agent.description": "Create and manage your own AI agents.",
|
||||
"admin.permissions.permission.manage_own_agent.name": "Manage own AI agents",
|
||||
"admin.permissions.permission.manage_own_incoming_webhooks.description": "Create, edit, and delete your own incoming webhooks.",
|
||||
"admin.permissions.permission.manage_own_incoming_webhooks.name": "Manage Own",
|
||||
"admin.permissions.permission.manage_own_outgoing_webhooks.description": "Create, edit, and delete your own outgoing webhooks.",
|
||||
|
||||
@@ -267,6 +267,8 @@ const values = {
|
||||
MANAGE_SECURE_CONNECTIONS: 'manage_secure_connections',
|
||||
|
||||
CREATE_CUSTOM_GROUP: 'create_custom_group',
|
||||
MANAGE_OWN_AGENT: 'manage_own_agent',
|
||||
MANAGE_OTHERS_AGENT: 'manage_others_agent',
|
||||
MANAGE_CUSTOM_GROUP_MEMBERS: 'manage_custom_group_members',
|
||||
EDIT_CUSTOM_GROUP: 'edit_custom_group',
|
||||
DELETE_CUSTOM_GROUP: 'delete_custom_group',
|
||||
|
||||
@@ -1217,6 +1217,8 @@ export const PermissionsScope = {
|
||||
[Permissions.DELETE_CUSTOM_GROUP]: 'system_scope',
|
||||
[Permissions.RESTORE_CUSTOM_GROUP]: 'system_scope',
|
||||
[Permissions.MANAGE_CUSTOM_GROUP_MEMBERS]: 'system_scope',
|
||||
[Permissions.MANAGE_OWN_AGENT]: 'system_scope',
|
||||
[Permissions.MANAGE_OTHERS_AGENT]: 'system_scope',
|
||||
[Permissions.USE_SLASH_COMMANDS]: 'channel_scope',
|
||||
[Permissions.ADD_BOOKMARK_PUBLIC_CHANNEL]: 'channel_scope',
|
||||
[Permissions.EDIT_BOOKMARK_PUBLIC_CHANNEL]: 'channel_scope',
|
||||
@@ -1280,6 +1282,7 @@ export const DefaultRolePermissions = {
|
||||
Permissions.CREATE_EMOJIS,
|
||||
Permissions.RUN_VIEW,
|
||||
Permissions.RESTORE_CUSTOM_GROUP,
|
||||
Permissions.MANAGE_OWN_AGENT,
|
||||
Permissions.ADD_BOOKMARK_PUBLIC_CHANNEL,
|
||||
Permissions.EDIT_BOOKMARK_PUBLIC_CHANNEL,
|
||||
Permissions.DELETE_BOOKMARK_PUBLIC_CHANNEL,
|
||||
|
||||
Reference in New Issue
Block a user