From 5ec78546e2cdf372039e5f79a8453c6ec7664e14 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Tue, 7 Dec 2021 09:43:33 -0800 Subject: [PATCH] MM-38150 Adding playbooks permissions (#19096) * Add playbooks related permissions. * Add RolesGrantPermission to pluginapi * Fixing scopes. * New defaults. * Fix defaults * Fix tests. * Fix migration. * More test and migration fixes. * Need to add everything to system admin too. * Move to 63 * Feedback fixes. * Fix system manager editing playbook permissions. --- api4/role.go | 13 ++- app/app_test.go | 4 + app/migrations.go | 128 ++++++++++++++++++++++++ app/permissions_migrations.go | 36 +++++++ app/plugin_api.go | 4 + app/scheme.go | 4 + model/migration.go | 1 + model/permission.go | 146 +++++++++++++++++++++++++++- model/role.go | 65 +++++++++++++ model/scheme.go | 102 ++++++++++++------- plugin/api.go | 5 + plugin/api_timer_layer_generated.go | 7 ++ plugin/client_rpc_generated.go | 29 ++++++ plugin/plugintest/api.go | 14 +++ store/sqlstore/integrity_test.go | 32 ++++++ store/sqlstore/scheme_store.go | 100 +++++++++++++++---- store/sqlstore/upgrade.go | 5 + store/storetest/scheme_store.go | 32 ++++++ testlib/store.go | 2 + 19 files changed, 670 insertions(+), 59 deletions(-) diff --git a/api4/role.go b/api4/role.go index c004f22402..15cbfd04cd 100644 --- a/api4/role.go +++ b/api4/role.go @@ -178,7 +178,18 @@ func patchRole(c *Context, w http.ResponseWriter, r *http.Request) { return } - if oldRole.Name == model.TeamAdminRoleId || oldRole.Name == model.ChannelAdminRoleId || oldRole.Name == model.SystemUserRoleId || oldRole.Name == model.TeamUserRoleId || oldRole.Name == model.ChannelUserRoleId || oldRole.Name == model.SystemGuestRoleId || oldRole.Name == model.TeamGuestRoleId || oldRole.Name == model.ChannelGuestRoleId { + if oldRole.Name == model.TeamAdminRoleId || + oldRole.Name == model.ChannelAdminRoleId || + oldRole.Name == model.SystemUserRoleId || + oldRole.Name == model.TeamUserRoleId || + oldRole.Name == model.ChannelUserRoleId || + oldRole.Name == model.SystemGuestRoleId || + oldRole.Name == model.TeamGuestRoleId || + oldRole.Name == model.ChannelGuestRoleId || + oldRole.Name == model.PlaybookAdminRoleId || + oldRole.Name == model.PlaybookMemberRoleId || + oldRole.Name == model.RunAdminRoleId || + oldRole.Name == model.RunMemberRoleId { if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionSysconsoleWriteUserManagementPermissions) { c.SetPermissionError(model.PermissionSysconsoleWriteUserManagementPermissions) return diff --git a/app/app_test.go b/app/app_test.go index 438884291b..310cd86076 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -89,6 +89,10 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { "system_user_access_token", "team_post_all", "team_post_all_public", + "playbook_admin", + "playbook_member", + "run_admin", + "run_member", } roles1, err1 := th.App.GetRolesByNames(roleNames) diff --git a/app/migrations.go b/app/migrations.go index 460c5f3ebd..dc95d15892 100644 --- a/app/migrations.go +++ b/app/migrations.go @@ -16,6 +16,7 @@ const EmojisPermissionsMigrationKey = "EmojisPermissionsMigrationComplete" const GuestRolesCreationMigrationKey = "GuestRolesCreationMigrationComplete" const SystemConsoleRolesCreationMigrationKey = "SystemConsoleRolesCreationMigrationComplete" const ContentExtractionConfigDefaultTrueMigrationKey = "ContentExtractionConfigDefaultTrueMigrationComplete" +const PlaybookRolesCreationMigrationKey = "PlaybookRolesCreationMigrationComplete" // This function migrates the default built in roles from code/config to the database. func (a *App) DoAdvancedPermissionsMigration() { @@ -307,6 +308,132 @@ func (s *Server) doContentExtractionConfigDefaultTrueMigration() { } } +func (s *Server) doPlaybooksRolesCreationMigration() { + // If the migration is already marked as completed, don't do it again. + if _, err := s.Store.System().GetByName(PlaybookRolesCreationMigrationKey); err == nil { + return + } + + roles := model.MakeDefaultRoles() + + allSucceeded := true + if _, err := s.Store.Role().GetByName(context.Background(), model.PlaybookAdminRoleId); err != nil { + if _, err := s.Store.Role().Save(roles[model.PlaybookAdminRoleId]); err != nil { + mlog.Critical("Failed to create new playbook admin role to database.", mlog.Err(err)) + allSucceeded = false + } + } + if _, err := s.Store.Role().GetByName(context.Background(), model.PlaybookMemberRoleId); err != nil { + if _, err := s.Store.Role().Save(roles[model.PlaybookMemberRoleId]); err != nil { + mlog.Critical("Failed to create new playbook member role to database.", mlog.Err(err)) + allSucceeded = false + } + } + if _, err := s.Store.Role().GetByName(context.Background(), model.RunAdminRoleId); err != nil { + if _, err := s.Store.Role().Save(roles[model.RunAdminRoleId]); err != nil { + mlog.Critical("Failed to create new run admin role to database.", mlog.Err(err)) + allSucceeded = false + } + } + if _, err := s.Store.Role().GetByName(context.Background(), model.RunMemberRoleId); err != nil { + if _, err := s.Store.Role().Save(roles[model.RunMemberRoleId]); err != nil { + mlog.Critical("Failed to create new run member role to database.", mlog.Err(err)) + allSucceeded = false + } + } + schemes, err := s.Store.Scheme().GetAllPage(model.SchemeScopeTeam, 0, 1000000) + if err != nil { + mlog.Critical("Failed to get all schemes.", mlog.Err(err)) + allSucceeded = false + } + + for _, scheme := range schemes { + if scheme.Scope == model.SchemeScopeTeam { + if scheme.DefaultPlaybookAdminRole == "" { + playbookAdminRole := &model.Role{ + Name: model.NewId(), + DisplayName: fmt.Sprintf("Playbook Admin Role for Scheme %s", scheme.Name), + Permissions: roles[model.PlaybookAdminRoleId].Permissions, + SchemeManaged: true, + } + + if savedRole, err := s.Store.Role().Save(playbookAdminRole); err != nil { + mlog.Critical("Failed to create new playbook admin role for existing custom scheme.", mlog.Err(err)) + allSucceeded = false + } else { + scheme.DefaultPlaybookAdminRole = savedRole.Name + } + } + if scheme.DefaultPlaybookMemberRole == "" { + playbookMember := &model.Role{ + Name: model.NewId(), + DisplayName: fmt.Sprintf("Playbook Member Role for Scheme %s", scheme.Name), + Permissions: roles[model.PlaybookMemberRoleId].Permissions, + SchemeManaged: true, + } + + if savedRole, err := s.Store.Role().Save(playbookMember); err != nil { + mlog.Critical("Failed to create new playbook member role for existing custom scheme.", mlog.Err(err)) + allSucceeded = false + } else { + scheme.DefaultPlaybookMemberRole = savedRole.Name + } + } + + if scheme.DefaultRunAdminRole == "" { + runAdminRole := &model.Role{ + Name: model.NewId(), + DisplayName: fmt.Sprintf("Run Admin Role for Scheme %s", scheme.Name), + Permissions: roles[model.RunAdminRoleId].Permissions, + SchemeManaged: true, + } + + if savedRole, err := s.Store.Role().Save(runAdminRole); err != nil { + mlog.Critical("Failed to create new run admin role for existing custom scheme.", mlog.Err(err)) + allSucceeded = false + } else { + scheme.DefaultRunAdminRole = savedRole.Name + } + } + + if scheme.DefaultRunMemberRole == "" { + runMemberRole := &model.Role{ + Name: model.NewId(), + DisplayName: fmt.Sprintf("Run Member Role for Scheme %s", scheme.Name), + Permissions: roles[model.RunMemberRoleId].Permissions, + SchemeManaged: true, + } + + if savedRole, err := s.Store.Role().Save(runMemberRole); err != nil { + mlog.Critical("Failed to create new run member role for existing custom scheme.", mlog.Err(err)) + allSucceeded = false + } else { + scheme.DefaultRunMemberRole = savedRole.Name + } + } + _, err := s.Store.Scheme().Save(scheme) + if err != nil { + mlog.Critical("Failed to update custom scheme.", mlog.Err(err)) + allSucceeded = false + } + } + } + + if !allSucceeded { + return + } + + system := model.System{ + Name: PlaybookRolesCreationMigrationKey, + Value: "true", + } + + if err := s.Store.System().Save(&system); err != nil { + mlog.Critical("Failed to mark playbook roles creation migration as completed.", mlog.Err(err)) + } + +} + func (a *App) DoAppMigrations() { a.Srv().doAppMigrations() } @@ -323,4 +450,5 @@ func (s *Server) doAppMigrations() { mlog.Critical("(app.App).DoPermissionsMigrations failed", mlog.Err(err)) } s.doContentExtractionConfigDefaultTrueMigration() + s.doPlaybooksRolesCreationMigration() } diff --git a/app/permissions_migrations.go b/app/permissions_migrations.go index 249728ee0b..540d5fce71 100644 --- a/app/permissions_migrations.go +++ b/app/permissions_migrations.go @@ -915,6 +915,41 @@ func (a *App) getAddTestEmailAncillaryPermission() (permissionsMap, error) { return transformations, nil } +func (a *App) getAddPlaybooksPermissions() (permissionsMap, error) { + transformations := []permissionTransformation{} + + transformations = append(transformations, permissionTransformation{ + On: permissionOr( + permissionExists(model.PermissionCreatePublicChannel.Id), + permissionExists(model.PermissionCreatePrivateChannel.Id), + ), + Add: []string{ + model.PermissionPublicPlaybookCreate.Id, + model.PermissionPrivatePlaybookCreate.Id, + }, + }) + + transformations = append(transformations, permissionTransformation{ + On: isRole(model.SystemAdminRoleId), + Add: []string{ + model.PermissionPublicPlaybookManageProperties.Id, + model.PermissionPublicPlaybookManageMembers.Id, + model.PermissionPublicPlaybookView.Id, + model.PermissionPublicPlaybookMakePrivate.Id, + model.PermissionPrivatePlaybookManageProperties.Id, + model.PermissionPrivatePlaybookManageMembers.Id, + model.PermissionPrivatePlaybookView.Id, + model.PermissionPrivatePlaybookMakePublic.Id, + model.PermissionRunCreate.Id, + model.PermissionRunManageProperties.Id, + model.PermissionRunManageMembers.Id, + model.PermissionRunView.Id, + }, + }) + + return transformations, nil +} + // DoPermissionsMigrations execute all the permissions migrations need by the current version. func (a *App) DoPermissionsMigrations() error { return a.Srv().doPermissionsMigrations() @@ -953,6 +988,7 @@ func (s *Server) doPermissionsMigrations() error { {Key: model.MigrationKeyAddAboutSubsectionPermissions, Migration: a.getAddAboutSubsectionPermissions}, {Key: model.MigrationKeyAddReportingSubsectionPermissions, Migration: a.getAddReportingSubsectionPermissions}, {Key: model.MigrationKeyAddTestEmailAncillaryPermission, Migration: a.getAddTestEmailAncillaryPermission}, + {Key: model.MigrationKeyAddPlaybooksPermissions, Migration: a.getAddPlaybooksPermissions}, } roles, err := s.Store.Role().GetAll() diff --git a/app/plugin_api.go b/app/plugin_api.go index 845911b47f..44f7113a1a 100644 --- a/app/plugin_api.go +++ b/app/plugin_api.go @@ -914,6 +914,10 @@ func (api *PluginAPI) HasPermissionToChannel(userID, channelID string, permissio return api.app.HasPermissionToChannel(userID, channelID, permission) } +func (api *PluginAPI) RolesGrantPermission(roleNames []string, permissionId string) bool { + return api.app.RolesGrantPermission(roleNames, permissionId) +} + func (api *PluginAPI) LogDebug(msg string, keyValuePairs ...interface{}) { api.logger.Debugw(msg, keyValuePairs...) } diff --git a/app/scheme.go b/app/scheme.go index 34b728bde0..460e32f998 100644 --- a/app/scheme.go +++ b/app/scheme.go @@ -83,6 +83,10 @@ func (a *App) CreateScheme(scheme *model.Scheme) (*model.Scheme, *model.AppError scheme.DefaultChannelAdminRole = "" scheme.DefaultChannelUserRole = "" scheme.DefaultChannelGuestRole = "" + scheme.DefaultPlaybookAdminRole = "" + scheme.DefaultPlaybookMemberRole = "" + scheme.DefaultRunAdminRole = "" + scheme.DefaultRunMemberRole = "" scheme.CreateAt = 0 scheme.UpdateAt = 0 scheme.DeleteAt = 0 diff --git a/model/migration.go b/model/migration.go index 23afe1237c..361eca69a9 100644 --- a/model/migration.go +++ b/model/migration.go @@ -36,4 +36,5 @@ const ( MigrationKeyAddAboutSubsectionPermissions = "about_subsection_permissions" MigrationKeyAddIntegrationsSubsectionPermissions = "integrations_subsection_permissions" MigrationKeyFixCRTChannelUnreads = "fix_crt_channel_unreads" + MigrationKeyAddPlaybooksPermissions = "playbooks_permissions" ) diff --git a/model/permission.go b/model/permission.go index 6c10bab053..9a3e4aae17 100644 --- a/model/permission.go +++ b/model/permission.go @@ -4,9 +4,11 @@ package model const ( - PermissionScopeSystem = "system_scope" - PermissionScopeTeam = "team_scope" - PermissionScopeChannel = "channel_scope" + PermissionScopeSystem = "system_scope" + PermissionScopeTeam = "team_scope" + PermissionScopeChannel = "channel_scope" + PermissionScopePlaybook = "playbook_scope" + PermissionScopeRun = "run_scope" ) type Permission struct { @@ -331,6 +333,23 @@ var PermissionSysconsoleWriteExperimentalFeatureFlags *Permission var PermissionSysconsoleReadExperimentalBleve *Permission var PermissionSysconsoleWriteExperimentalBleve *Permission +var PermissionPublicPlaybookCreate *Permission +var PermissionPublicPlaybookManageProperties *Permission +var PermissionPublicPlaybookManageMembers *Permission +var PermissionPublicPlaybookView *Permission +var PermissionPublicPlaybookMakePrivate *Permission + +var PermissionPrivatePlaybookCreate *Permission +var PermissionPrivatePlaybookManageProperties *Permission +var PermissionPrivatePlaybookManageMembers *Permission +var PermissionPrivatePlaybookView *Permission +var PermissionPrivatePlaybookMakePublic *Permission + +var PermissionRunCreate *Permission +var PermissionRunManageProperties *Permission +var PermissionRunManageMembers *Permission +var PermissionRunView *Permission + // General permission that encompasses all system admin functions // in the future this could be broken up to allow access to some // admin functions but not others @@ -1895,6 +1914,105 @@ func initializePermissions() { PermissionScopeSystem, } + // Playbooks + PermissionPublicPlaybookCreate = &Permission{ + "playbook_public_create", + "", + "", + PermissionScopeTeam, + } + + PermissionPublicPlaybookManageProperties = &Permission{ + "playbook_public_manage_properties", + "", + "", + PermissionScopePlaybook, + } + + PermissionPublicPlaybookManageMembers = &Permission{ + "playbook_public_manage_members", + "", + "", + PermissionScopePlaybook, + } + + PermissionPublicPlaybookView = &Permission{ + "playbook_public_view", + "", + "", + PermissionScopePlaybook, + } + + PermissionPublicPlaybookMakePrivate = &Permission{ + "playbook_public_make_private", + "", + "", + PermissionScopePlaybook, + } + + PermissionPrivatePlaybookCreate = &Permission{ + "playbook_private_create", + "", + "", + PermissionScopeTeam, + } + + PermissionPrivatePlaybookManageProperties = &Permission{ + "playbook_private_manage_properties", + "", + "", + PermissionScopePlaybook, + } + + PermissionPrivatePlaybookManageMembers = &Permission{ + "playbook_private_manage_members", + "", + "", + PermissionScopePlaybook, + } + + PermissionPrivatePlaybookView = &Permission{ + "playbook_private_view", + "", + "", + PermissionScopePlaybook, + } + + PermissionPrivatePlaybookMakePublic = &Permission{ + "playbook_private_make_public", + "", + "", + PermissionScopePlaybook, + } + + PermissionRunCreate = &Permission{ + "run_create", + "", + "", + PermissionScopePlaybook, + } + + PermissionRunManageProperties = &Permission{ + "run_manage_properties", + "", + "", + PermissionScopeRun, + } + + PermissionRunManageMembers = &Permission{ + "run_manage_members", + "", + "", + PermissionScopeRun, + } + + PermissionRunView = &Permission{ + "run_view", + "", + "", + PermissionScopeRun, + } + SysconsoleReadPermissions = []*Permission{ PermissionSysconsoleReadAboutEditionAndLicense, PermissionSysconsoleReadBilling, @@ -2108,6 +2226,8 @@ func initializePermissions() { PermissionViewTeam, PermissionViewMembers, PermissionInviteGuest, + PermissionPublicPlaybookCreate, + PermissionPrivatePlaybookCreate, } ChannelScopedPermissions := []*Permission{ @@ -2163,12 +2283,32 @@ func initializePermissions() { PermissionSysconsoleWriteCompliance, } + PlaybookScopedPermissions := []*Permission{ + PermissionPublicPlaybookManageProperties, + PermissionPublicPlaybookManageMembers, + PermissionPublicPlaybookView, + PermissionPublicPlaybookMakePrivate, + PermissionPrivatePlaybookManageProperties, + PermissionPrivatePlaybookManageMembers, + PermissionPrivatePlaybookView, + PermissionPrivatePlaybookMakePublic, + PermissionRunCreate, + } + + RunScopedPermissions := []*Permission{ + PermissionRunManageProperties, + PermissionRunManageMembers, + PermissionRunView, + } + AllPermissions = []*Permission{} AllPermissions = append(AllPermissions, SystemScopedPermissionsMinusSysconsole...) AllPermissions = append(AllPermissions, TeamScopedPermissions...) AllPermissions = append(AllPermissions, ChannelScopedPermissions...) AllPermissions = append(AllPermissions, SysconsoleReadPermissions...) AllPermissions = append(AllPermissions, SysconsoleWritePermissions...) + AllPermissions = append(AllPermissions, PlaybookScopedPermissions...) + AllPermissions = append(AllPermissions, RunScopedPermissions...) ChannelModeratedPermissions = []string{ PermissionCreatePost.Id, diff --git a/model/role.go b/model/role.go index 68697838f7..b8d75d4b4c 100644 --- a/model/role.go +++ b/model/role.go @@ -41,6 +41,11 @@ func init() { ChannelGuestRoleId, ChannelUserRoleId, ChannelAdminRoleId, + + PlaybookAdminRoleId, + PlaybookMemberRoleId, + RunAdminRoleId, + RunMemberRoleId, }, NewSystemRoleIDs...) // When updating the values here, the values in mattermost-redux must also be updated. @@ -362,6 +367,11 @@ const ( ChannelUserRoleId = "channel_user" ChannelAdminRoleId = "channel_admin" + PlaybookAdminRoleId = "playbook_admin" + PlaybookMemberRoleId = "playbook_member" + RunAdminRoleId = "run_admin" + RunMemberRoleId = "run_member" + RoleNameMaxLength = 64 RoleDisplayNameMaxLength = 128 RoleDescriptionMaxLength = 1024 @@ -807,6 +817,61 @@ func MakeDefaultRoles() map[string]*Role { BuiltIn: true, } + roles[PlaybookAdminRoleId] = &Role{ + Name: PlaybookAdminRoleId, + DisplayName: "authentication.roles.playbook_admin.name", + Description: "authentication.roles.playbook_admin.description", + Permissions: []string{ + PermissionPublicPlaybookManageMembers.Id, + PermissionPublicPlaybookManageProperties.Id, + PermissionPrivatePlaybookManageMembers.Id, + PermissionPrivatePlaybookManageProperties.Id, + PermissionPublicPlaybookMakePrivate.Id, + }, + SchemeManaged: true, + BuiltIn: true, + } + + roles[PlaybookMemberRoleId] = &Role{ + Name: PlaybookMemberRoleId, + DisplayName: "authentication.roles.playbook_member.name", + Description: "authentication.roles.playbook_member.description", + Permissions: []string{ + PermissionPublicPlaybookView.Id, + PermissionPublicPlaybookManageMembers.Id, + PermissionPublicPlaybookManageProperties.Id, + PermissionPrivatePlaybookView.Id, + PermissionPrivatePlaybookManageMembers.Id, + PermissionPrivatePlaybookManageProperties.Id, + PermissionRunCreate.Id, + }, + SchemeManaged: true, + BuiltIn: true, + } + + roles[RunAdminRoleId] = &Role{ + Name: RunAdminRoleId, + DisplayName: "authentication.roles.run_admin.name", + Description: "authentication.roles.run_admin.description", + Permissions: []string{ + PermissionRunManageMembers.Id, + PermissionRunManageProperties.Id, + }, + SchemeManaged: true, + BuiltIn: true, + } + + roles[RunMemberRoleId] = &Role{ + Name: RunMemberRoleId, + DisplayName: "authentication.roles.run_member.name", + Description: "authentication.roles.run_member.description", + Permissions: []string{ + PermissionRunView.Id, + }, + SchemeManaged: true, + BuiltIn: true, + } + roles[SystemGuestRoleId] = &Role{ Name: "system_guest", DisplayName: "authentication.roles.global_guest.name", diff --git a/model/scheme.go b/model/scheme.go index c861c1e66e..b2bc713c6d 100644 --- a/model/scheme.go +++ b/model/scheme.go @@ -14,23 +14,29 @@ const ( SchemeDescriptionMaxLength = 1024 SchemeScopeTeam = "team" SchemeScopeChannel = "channel" + SchemeScopePlaybook = "playbook" + SchemeScopeRun = "run" ) type Scheme struct { - Id string `json:"id"` - Name string `json:"name"` - DisplayName string `json:"display_name"` - Description string `json:"description"` - CreateAt int64 `json:"create_at"` - UpdateAt int64 `json:"update_at"` - DeleteAt int64 `json:"delete_at"` - Scope string `json:"scope"` - DefaultTeamAdminRole string `json:"default_team_admin_role"` - DefaultTeamUserRole string `json:"default_team_user_role"` - DefaultChannelAdminRole string `json:"default_channel_admin_role"` - DefaultChannelUserRole string `json:"default_channel_user_role"` - DefaultTeamGuestRole string `json:"default_team_guest_role"` - DefaultChannelGuestRole string `json:"default_channel_guest_role"` + Id string `json:"id"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + CreateAt int64 `json:"create_at"` + UpdateAt int64 `json:"update_at"` + DeleteAt int64 `json:"delete_at"` + Scope string `json:"scope"` + DefaultTeamAdminRole string `json:"default_team_admin_role"` + DefaultTeamUserRole string `json:"default_team_user_role"` + DefaultChannelAdminRole string `json:"default_channel_admin_role"` + DefaultChannelUserRole string `json:"default_channel_user_role"` + DefaultTeamGuestRole string `json:"default_team_guest_role"` + DefaultChannelGuestRole string `json:"default_channel_guest_role"` + DefaultPlaybookAdminRole string `json:"default_playbook_admin_role"` + DefaultPlaybookMemberRole string `json:"default_playbook_member_role"` + DefaultRunAdminRole string `json:"default_run_admin_role"` + DefaultRunMemberRole string `json:"default_run_member_role"` } type SchemePatch struct { @@ -45,31 +51,39 @@ type SchemeIDPatch struct { // SchemeConveyor is used for importing and exporting a Scheme and its associated Roles. type SchemeConveyor struct { - Name string `json:"name"` - DisplayName string `json:"display_name"` - Description string `json:"description"` - Scope string `json:"scope"` - TeamAdmin string `json:"default_team_admin_role"` - TeamUser string `json:"default_team_user_role"` - TeamGuest string `json:"default_team_guest_role"` - ChannelAdmin string `json:"default_channel_admin_role"` - ChannelUser string `json:"default_channel_user_role"` - ChannelGuest string `json:"default_channel_guest_role"` - Roles []*Role `json:"roles"` + Name string `json:"name"` + DisplayName string `json:"display_name"` + Description string `json:"description"` + Scope string `json:"scope"` + TeamAdmin string `json:"default_team_admin_role"` + TeamUser string `json:"default_team_user_role"` + TeamGuest string `json:"default_team_guest_role"` + ChannelAdmin string `json:"default_channel_admin_role"` + ChannelUser string `json:"default_channel_user_role"` + ChannelGuest string `json:"default_channel_guest_role"` + PlaybookAdmin string `json:"default_playbook_admin_role"` + PlaybookMember string `json:"default_playbook_member_role"` + RunAdmin string `json:"default_run_admin_role"` + RunMember string `json:"default_run_member_role"` + Roles []*Role `json:"roles"` } func (sc *SchemeConveyor) Scheme() *Scheme { return &Scheme{ - DisplayName: sc.DisplayName, - Name: sc.Name, - Description: sc.Description, - Scope: sc.Scope, - DefaultTeamAdminRole: sc.TeamAdmin, - DefaultTeamUserRole: sc.TeamUser, - DefaultTeamGuestRole: sc.TeamGuest, - DefaultChannelAdminRole: sc.ChannelAdmin, - DefaultChannelUserRole: sc.ChannelUser, - DefaultChannelGuestRole: sc.ChannelGuest, + DisplayName: sc.DisplayName, + Name: sc.Name, + Description: sc.Description, + Scope: sc.Scope, + DefaultTeamAdminRole: sc.TeamAdmin, + DefaultTeamUserRole: sc.TeamUser, + DefaultTeamGuestRole: sc.TeamGuest, + DefaultChannelAdminRole: sc.ChannelAdmin, + DefaultChannelUserRole: sc.ChannelUser, + DefaultChannelGuestRole: sc.ChannelGuest, + DefaultPlaybookAdminRole: sc.PlaybookAdmin, + DefaultPlaybookMemberRole: sc.PlaybookMember, + DefaultRunAdminRole: sc.RunAdmin, + DefaultRunMemberRole: sc.RunMember, } } @@ -101,7 +115,7 @@ func (scheme *Scheme) IsValidForCreate() bool { } switch scheme.Scope { - case SchemeScopeTeam, SchemeScopeChannel: + case SchemeScopeTeam, SchemeScopeChannel, SchemeScopePlaybook, SchemeScopeRun: default: return false } @@ -130,6 +144,22 @@ func (scheme *Scheme) IsValidForCreate() bool { if !IsValidRoleName(scheme.DefaultTeamGuestRole) { return false } + + if !IsValidRoleName(scheme.DefaultPlaybookAdminRole) { + return false + } + + if !IsValidRoleName(scheme.DefaultPlaybookMemberRole) { + return false + } + + if !IsValidRoleName(scheme.DefaultRunAdminRole) { + return false + } + + if !IsValidRoleName(scheme.DefaultRunMemberRole) { + return false + } } if scheme.Scope == SchemeScopeChannel { diff --git a/plugin/api.go b/plugin/api.go index cd48b76835..45b3a51196 100644 --- a/plugin/api.go +++ b/plugin/api.go @@ -960,6 +960,11 @@ type API interface { // Minimum server version: 5.3 HasPermissionToChannel(userID, channelId string, permission *model.Permission) bool + // RolesGrantPermission check if the specified roles grant the specified permission + // + // Minimum server version: 6.3 + RolesGrantPermission(roleNames []string, permissionId string) bool + // LogDebug writes a log message to the Mattermost server log file. // Appropriate context such as the plugin name will already be added as fields so plugins // do not need to add that info. diff --git a/plugin/api_timer_layer_generated.go b/plugin/api_timer_layer_generated.go index 67fc99634d..d271797907 100644 --- a/plugin/api_timer_layer_generated.go +++ b/plugin/api_timer_layer_generated.go @@ -1034,6 +1034,13 @@ func (api *apiTimerLayer) HasPermissionToChannel(userID, channelId string, permi return _returnsA } +func (api *apiTimerLayer) RolesGrantPermission(roleNames []string, permissionId string) bool { + startTime := timePkg.Now() + _returnsA := api.apiImpl.RolesGrantPermission(roleNames, permissionId) + api.recordTime(startTime, "RolesGrantPermission", true) + return _returnsA +} + func (api *apiTimerLayer) LogDebug(msg string, keyValuePairs ...interface{}) { startTime := timePkg.Now() api.apiImpl.LogDebug(msg, keyValuePairs...) diff --git a/plugin/client_rpc_generated.go b/plugin/client_rpc_generated.go index 8ee7b6c89c..7c05e90c85 100644 --- a/plugin/client_rpc_generated.go +++ b/plugin/client_rpc_generated.go @@ -4832,6 +4832,35 @@ func (s *apiRPCServer) HasPermissionToChannel(args *Z_HasPermissionToChannelArgs return nil } +type Z_RolesGrantPermissionArgs struct { + A []string + B string +} + +type Z_RolesGrantPermissionReturns struct { + A bool +} + +func (g *apiRPCClient) RolesGrantPermission(roleNames []string, permissionId string) bool { + _args := &Z_RolesGrantPermissionArgs{roleNames, permissionId} + _returns := &Z_RolesGrantPermissionReturns{} + if err := g.client.Call("Plugin.RolesGrantPermission", _args, _returns); err != nil { + log.Printf("RPC call to RolesGrantPermission API failed: %s", err.Error()) + } + return _returns.A +} + +func (s *apiRPCServer) RolesGrantPermission(args *Z_RolesGrantPermissionArgs, returns *Z_RolesGrantPermissionReturns) error { + if hook, ok := s.impl.(interface { + RolesGrantPermission(roleNames []string, permissionId string) bool + }); ok { + returns.A = hook.RolesGrantPermission(args.A, args.B) + } else { + return encodableError(fmt.Errorf("API RolesGrantPermission called but not implemented.")) + } + return nil +} + type Z_SendMailArgs struct { A string B string diff --git a/plugin/plugintest/api.go b/plugin/plugintest/api.go index 5345ea0413..d32b8a063f 100644 --- a/plugin/plugintest/api.go +++ b/plugin/plugintest/api.go @@ -3023,6 +3023,20 @@ func (_m *API) RevokeUserAccessToken(tokenID string) *model.AppError { return r0 } +// RolesGrantPermission provides a mock function with given fields: roleNames, permissionId +func (_m *API) RolesGrantPermission(roleNames []string, permissionId string) bool { + ret := _m.Called(roleNames, permissionId) + + var r0 bool + if rf, ok := ret.Get(0).(func([]string, string) bool); ok { + r0 = rf(roleNames, permissionId) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // SaveConfig provides a mock function with given fields: config func (_m *API) SaveConfig(config *model.Config) *model.AppError { ret := _m.Called(config) diff --git a/store/sqlstore/integrity_test.go b/store/sqlstore/integrity_test.go index a26bddace7..f292833d3b 100644 --- a/store/sqlstore/integrity_test.go +++ b/store/sqlstore/integrity_test.go @@ -269,6 +269,38 @@ func createDefaultRoles(ss store.Store) { model.PermissionCreatePost.Id, }, }) + + ss.Role().Save(&model.Role{ + Name: model.PlaybookAdminRoleId, + DisplayName: model.PlaybookAdminRoleId, + Permissions: []string{ + model.PermissionPrivatePlaybookManageMembers.Id, + }, + }) + + ss.Role().Save(&model.Role{ + Name: model.PlaybookMemberRoleId, + DisplayName: model.PlaybookMemberRoleId, + Permissions: []string{ + model.PermissionPrivatePlaybookManageMembers.Id, + }, + }) + + ss.Role().Save(&model.Role{ + Name: model.RunAdminRoleId, + DisplayName: model.RunAdminRoleId, + Permissions: []string{ + model.PermissionRunManageMembers.Id, + }, + }) + + ss.Role().Save(&model.Role{ + Name: model.RunMemberRoleId, + DisplayName: model.RunMemberRoleId, + Permissions: []string{ + model.PermissionRunManageMembers.Id, + }, + }) } func createScheme(ss store.Store) *model.Scheme { diff --git a/store/sqlstore/scheme_store.go b/store/sqlstore/scheme_store.go index 2c025e8c81..156cdaa789 100644 --- a/store/sqlstore/scheme_store.go +++ b/store/sqlstore/scheme_store.go @@ -34,6 +34,10 @@ func newSqlSchemeStore(sqlStore *SqlStore) store.SchemeStore { table.ColMap("DefaultChannelAdminRole").SetMaxSize(64) table.ColMap("DefaultChannelUserRole").SetMaxSize(64) table.ColMap("DefaultChannelGuestRole").SetMaxSize(64) + table.ColMap("DefaultPlaybookAdminRole").SetMaxSize(64).SetDefaultConstraint(model.NewString("")) + table.ColMap("DefaultPlaybookMemberRole").SetMaxSize(64).SetDefaultConstraint(model.NewString("")) + table.ColMap("DefaultRunAdminRole").SetMaxSize(64).SetDefaultConstraint(model.NewString("")) + table.ColMap("DefaultRunMemberRole").SetMaxSize(64).SetDefaultConstraint(model.NewString("")) } return s @@ -72,7 +76,8 @@ func (s *SqlSchemeStore) Save(scheme *model.Scheme) (*model.Scheme, error) { res, err := s.GetMasterX().NamedExec(`UPDATE Schemes SET UpdateAt=:UpdateAt, CreateAt=:CreateAt, DeleteAt=:DeleteAt, Name=:Name, DisplayName=:DisplayName, Description=:Description, Scope=:Scope, DefaultTeamAdminRole=:DefaultTeamAdminRole, DefaultTeamUserRole=:DefaultTeamUserRole, DefaultTeamGuestRole=:DefaultTeamGuestRole, - DefaultChannelAdminRole=:DefaultChannelAdminRole, DefaultChannelUserRole=:DefaultChannelUserRole, DefaultChannelGuestRole=:DefaultChannelGuestRole + DefaultChannelAdminRole=:DefaultChannelAdminRole, DefaultChannelUserRole=:DefaultChannelUserRole, DefaultChannelGuestRole=:DefaultChannelGuestRole, + DefaultPlaybookMemberRole=:DefaultPlaybookMemberRole, DefaultPlaybookAdminRole=:DefaultPlaybookAdminRole, DefaultRunMemberRole=:DefaultRunMemberRole, DefaultRunAdminRole=:DefaultRunAdminRole WHERE Id=:Id`, scheme) if err != nil { @@ -92,7 +97,18 @@ func (s *SqlSchemeStore) Save(scheme *model.Scheme) (*model.Scheme, error) { func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxWrapper) (*model.Scheme, error) { // Fetch the default system scheme roles to populate default permissions. - defaultRoleNames := []string{model.TeamAdminRoleId, model.TeamUserRoleId, model.TeamGuestRoleId, model.ChannelAdminRoleId, model.ChannelUserRoleId, model.ChannelGuestRoleId} + defaultRoleNames := []string{ + model.TeamAdminRoleId, + model.TeamUserRoleId, + model.TeamGuestRoleId, + model.ChannelAdminRoleId, + model.ChannelUserRoleId, + model.ChannelGuestRoleId, + model.PlaybookAdminRoleId, + model.PlaybookMemberRoleId, + model.RunAdminRoleId, + model.RunMemberRoleId, + } defaultRoles := make(map[string]*model.Role) roles, err := s.SqlStore.Role().GetByNames(defaultRoleNames) if err != nil { @@ -100,23 +116,10 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW } for _, role := range roles { - switch role.Name { - case model.TeamAdminRoleId: - defaultRoles[model.TeamAdminRoleId] = role - case model.TeamUserRoleId: - defaultRoles[model.TeamUserRoleId] = role - case model.TeamGuestRoleId: - defaultRoles[model.TeamGuestRoleId] = role - case model.ChannelAdminRoleId: - defaultRoles[model.ChannelAdminRoleId] = role - case model.ChannelUserRoleId: - defaultRoles[model.ChannelUserRoleId] = role - case model.ChannelGuestRoleId: - defaultRoles[model.ChannelGuestRoleId] = role - } + defaultRoles[role.Name] = role } - if len(defaultRoles) != 6 { + if len(defaultRoles) != len(defaultRoleNames) { return nil, errors.New("createScheme: unable to retrieve default scheme roles") } @@ -163,6 +166,58 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW return nil, err } scheme.DefaultTeamGuestRole = savedRole.Name + + // playbook admin role + playbookAdminRole := &model.Role{ + Name: model.NewId(), + DisplayName: fmt.Sprintf("Playbook Admin Role for Scheme %s", scheme.Name), + Permissions: defaultRoles[model.PlaybookAdminRoleId].Permissions, + SchemeManaged: true, + } + savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(playbookAdminRole, transaction) + if err != nil { + return nil, err + } + scheme.DefaultPlaybookAdminRole = savedRole.Name + + // playbook member role + playbookMemberRole := &model.Role{ + Name: model.NewId(), + DisplayName: fmt.Sprintf("Playbook Member Role for Scheme %s", scheme.Name), + Permissions: defaultRoles[model.PlaybookMemberRoleId].Permissions, + SchemeManaged: true, + } + savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(playbookMemberRole, transaction) + if err != nil { + return nil, err + } + scheme.DefaultPlaybookMemberRole = savedRole.Name + + // run admin role + runAdminRole := &model.Role{ + Name: model.NewId(), + DisplayName: fmt.Sprintf("Run Admin Role for Scheme %s", scheme.Name), + Permissions: defaultRoles[model.RunAdminRoleId].Permissions, + SchemeManaged: true, + } + savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(runAdminRole, transaction) + if err != nil { + return nil, err + } + scheme.DefaultRunAdminRole = savedRole.Name + + // run member role + runMemberRole := &model.Role{ + Name: model.NewId(), + DisplayName: fmt.Sprintf("Run Member Role for Scheme %s", scheme.Name), + Permissions: defaultRoles[model.RunMemberRoleId].Permissions, + SchemeManaged: true, + } + savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(runMemberRole, transaction) + if err != nil { + return nil, err + } + scheme.DefaultRunMemberRole = savedRole.Name } if scheme.Scope == model.SchemeScopeTeam || scheme.Scope == model.SchemeScopeChannel { @@ -234,9 +289,9 @@ func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *sqlxTxW } if _, err := transaction.NamedExec(`INSERT INTO Schemes - (Id, Name, DisplayName, Description, Scope, DefaultTeamAdminRole, DefaultTeamUserRole, DefaultTeamGuestRole, DefaultChannelAdminRole, DefaultChannelUserRole, DefaultChannelGuestRole, CreateAt, UpdateAt, DeleteAt) + (Id, Name, DisplayName, Description, Scope, DefaultTeamAdminRole, DefaultTeamUserRole, DefaultTeamGuestRole, DefaultChannelAdminRole, DefaultChannelUserRole, DefaultChannelGuestRole, CreateAt, UpdateAt, DeleteAt, DefaultPlaybookAdminRole, DefaultPlaybookMemberRole, DefaultRunAdminRole, DefaultRunMemberRole) VALUES - (:Id, :Name, :DisplayName, :Description, :Scope, :DefaultTeamAdminRole, :DefaultTeamUserRole, :DefaultTeamGuestRole, :DefaultChannelAdminRole, :DefaultChannelUserRole, :DefaultChannelGuestRole, :CreateAt, :UpdateAt, :DeleteAt)`, scheme); err != nil { + (:Id, :Name, :DisplayName, :Description, :Scope, :DefaultTeamAdminRole, :DefaultTeamUserRole, :DefaultTeamGuestRole, :DefaultChannelAdminRole, :DefaultChannelUserRole, :DefaultChannelGuestRole, :CreateAt, :UpdateAt, :DeleteAt, :DefaultPlaybookAdminRole, :DefaultPlaybookMemberRole, :DefaultRunAdminRole, :DefaultRunMemberRole)`, scheme); err != nil { return nil, errors.Wrap(err, "failed to save Scheme") } @@ -309,6 +364,13 @@ func (s *SqlSchemeStore) Delete(schemeId string) (*model.Scheme, error) { 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() diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go index b17cd59211..41ef921e4c 100644 --- a/store/sqlstore/upgrade.go +++ b/store/sqlstore/upgrade.go @@ -1421,6 +1421,11 @@ func upgradeDatabaseToVersion630(sqlStore *SqlStore) { // TODO: uncomment when the time arrive to upgrade the DB for 6.3 // if shouldPerformUpgrade(sqlStore, Version620, Version630) { + sqlStore.CreateColumnIfNotExists("Schemes", "DefaultPlaybookAdminRole", "VARCHAR(64)", "VARCHAR(64)", "") + sqlStore.CreateColumnIfNotExists("Schemes", "DefaultPlaybookMemberRole", "VARCHAR(64)", "VARCHAR(64)", "") + sqlStore.CreateColumnIfNotExists("Schemes", "DefaultRunAdminRole", "VARCHAR(64)", "VARCHAR(64)", "") + sqlStore.CreateColumnIfNotExists("Schemes", "DefaultRunMemberRole", "VARCHAR(64)", "VARCHAR(64)", "") + // saveSchemaVersion(sqlStore, Version630) // } } diff --git a/store/storetest/scheme_store.go b/store/storetest/scheme_store.go index 31da95af33..a63ab4c924 100644 --- a/store/storetest/scheme_store.go +++ b/store/storetest/scheme_store.go @@ -79,6 +79,38 @@ func createDefaultRoles(ss store.Store) { model.PermissionCreatePost.Id, }, }) + + ss.Role().Save(&model.Role{ + Name: model.PlaybookAdminRoleId, + DisplayName: model.PlaybookAdminRoleId, + Permissions: []string{ + model.PermissionPrivatePlaybookManageMembers.Id, + }, + }) + + ss.Role().Save(&model.Role{ + Name: model.PlaybookMemberRoleId, + DisplayName: model.PlaybookMemberRoleId, + Permissions: []string{ + model.PermissionPrivatePlaybookManageMembers.Id, + }, + }) + + ss.Role().Save(&model.Role{ + Name: model.RunAdminRoleId, + DisplayName: model.RunAdminRoleId, + Permissions: []string{ + model.PermissionRunManageMembers.Id, + }, + }) + + ss.Role().Save(&model.Role{ + Name: model.RunMemberRoleId, + DisplayName: model.RunMemberRoleId, + Permissions: []string{ + model.PermissionRunManageMembers.Id, + }, + }) } func testSchemeStoreSave(t *testing.T, ss store.Store) { diff --git a/testlib/store.go b/testlib/store.go index bf222f61e8..7d3437ef91 100644 --- a/testlib/store.go +++ b/testlib/store.go @@ -35,6 +35,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store { systemStore.On("GetByName", "EmojisPermissionsMigrationComplete").Return(&model.System{Name: "EmojisPermissionsMigrationComplete", Value: "true"}, nil) systemStore.On("GetByName", "GuestRolesCreationMigrationComplete").Return(&model.System{Name: "GuestRolesCreationMigrationComplete", Value: "true"}, nil) systemStore.On("GetByName", "SystemConsoleRolesCreationMigrationComplete").Return(&model.System{Name: "SystemConsoleRolesCreationMigrationComplete", Value: "true"}, nil) + systemStore.On("GetByName", "PlaybookRolesCreationMigrationComplete").Return(&model.System{Name: "PlaybookRolesCreationMigrationComplete", Value: "true"}, nil) systemStore.On("GetByName", model.MigrationKeyEmojiPermissionsSplit).Return(&model.System{Name: model.MigrationKeyEmojiPermissionsSplit, Value: "true"}, nil) systemStore.On("GetByName", model.MigrationKeyWebhookPermissionsSplit).Return(&model.System{Name: model.MigrationKeyWebhookPermissionsSplit, Value: "true"}, nil) systemStore.On("GetByName", model.MigrationKeyListJoinPublicPrivateTeams).Return(&model.System{Name: model.MigrationKeyListJoinPublicPrivateTeams, Value: "true"}, nil) @@ -62,6 +63,7 @@ func GetMockStoreForSetupFunctions() *mocks.Store { systemStore.On("GetByName", model.MigrationKeyAddIntegrationsSubsectionPermissions).Return(&model.System{Name: model.MigrationKeyAddIntegrationsSubsectionPermissions, Value: "true"}, nil) systemStore.On("GetByName", model.MigrationKeyAddManageSharedChannelPermissions).Return(&model.System{Name: model.MigrationKeyAddManageSharedChannelPermissions, Value: "true"}, nil) systemStore.On("GetByName", model.MigrationKeyAddManageSecureConnectionsPermissions).Return(&model.System{Name: model.MigrationKeyAddManageSecureConnectionsPermissions, Value: "true"}, nil) + systemStore.On("GetByName", model.MigrationKeyAddPlaybooksPermissions).Return(&model.System{Name: model.MigrationKeyAddPlaybooksPermissions, Value: "true"}, nil) systemStore.On("InsertIfExists", mock.AnythingOfType("*model.System")).Return(&model.System{}, nil).Once() systemStore.On("Save", mock.AnythingOfType("*model.System")).Return(nil)