Files
mattermost/store/sqlstore/scheme_store.go
Martin Kraft 8354206e5c MM-25543: New Admin Roles (#14960)
* MM-23832: Initial set of changes

* MM-23832: further iteration

* MM-23832: further iteration

* MM-23832: further iteration

* MM-23832: Fixes merge.

* create migration for new Roles

* MM-23832: Renames some roles.

* MM-23832: Adds ability to see logs.

* MM-23832: Removes manage roles from restricted admin.

* MM-23832: Make authentication section read-only for restricted admin.

* MM-23832: Allow restricted admin to purge caches.

* MM-23832: Adds ability to recycle DB connections.

* MM-23832: Adds ability to purge indexes.

* MM-23832: Adds ability to test email and S3 config.

* MM-23832: Adds abilituy to read job status.

* MM-23832: Adds ability to read plugin statuses.

* MM-23832: Renames Restricted Admin to System Manager.

* MM-23832: Adds manage team roles to system_user_manager.

* MM-23832: Updates some permissions.

* MM-23832: Allow get all channels and get moderations.

* MM-23832: Adds some permissions to User Manager.

* MM-23832: Remove write users from user manager.

* MM-23832: Changes permissions for the usermanagement > users sysconsole section.

* MM-23832: Removes read_settings and write_settings permissions. Ensures the usermanagement parent permissions encompass the sub-permissions.

* MM-23832: Updates permissions.

* MM-23832: Changes some permissions checks, adds new permissions to roles.

* MM-23832: Adds ability to update a role.

* MM-23832: Permissions updates.

* MM-23832: Removes write access to plugins for system manager.

* MM-23832: Removes read compliance from new roles.

* MM-23832: Adds mock for new roles creation migration.

* MM-23832: Changes to variadic param.

* MM-23832: Removes some duplication in the permissions model. Renames some permissions constants.

* MM-23832: Updates some migrations.

* MM-23832: Removes some unnecessary constants.

* MM-23832: Changes back to old app method name.

* MM-23832: Fixes incorrect permission check.

* MM-23832: Changes write to read permission check.

* MM-23832: Removes the authentication permission from link/unlink group.

* MM-23832: Enable testing LDAP with read permissions.

* MM-23832: Make testing elasticsearch a read permission.

* MM-23832: Warn metrics are associated to any system console read permissions.

* MM-23832: Updates some permissions checks.

* MM-23832: Removes non-systemconsole permissions from roles.

* MM-23832: Update default permission assignment of sysadmin.

* MM-23832: Fixes incorrect permission check. Removes some unused stuff.

* MM-23832: Update permission to check.

* MM-23832: Switches to struct tags.

* MM-23832: Adds some docs for the permissions tag.

* MM-23832: Removes whitespace.

* MM-23832: Combines system admin restricted access with other acess-control tag.

* MM-23832: Fixes some tests.

* MM-23832: Clarifies docs, does not assume prior permission check in '-' access value case.

* MM-23832: Updates to correct access tag value.

* MM-23832: Adds test of the config settings tag access.

* MM-23832: Undoes whitespace change.

* MM-23832: Removes comment.

* MM-23832: Adds the permissions to the new roles rather than using OR conditions on the permissions checks.

* MM-23832: Removes or condition on permission check.

* MM-23832: Updates mapping.

* MM-23832: Typo fix.

* MM-23832: Adds new 'read_jobs' permission.

* MM-23832: Add read_jobs to all roles with manage_jobs.

* MM-23832: Adds new permission read_other_users_teams.

* MM-23832: Adds read filtering of config.

* MM-23932: Change tag value.

* MM-23832: Fixes some tests. Adds test for read config access tag.

* MM-23832: Adds permissions to list teams.

* MM-23832: Removes the '-' tag value. Adds a new permission read_channel_groups. Updates a permission check.

* MM-23832: Removes unnecessary parent permission for user_management. Fixes permission check change error.

* MM-23832: Removes unused parameter to filter/merge function.

* MM-23832: Renames migration name.

* MM-23832: Fix for godoc.

* MM-23832: Fixes tests.

* MM-23832: Only makes a map once rather than every function call. Doesn't require access tag on config field structs. Reverts one test update and fixes another.

* MM-23832: Removes all of the unnecessary uses of (*App).SessionHasPermissionToAny since removing the user_management parent permission.

* MM-23832: Updates constant type.

* MM-23832: Removes unnecessary comment.

* MM-23832: Renames permissions.

* MM-23832: Fix for permission name changes.

* MM-23832: Adds missing config access tags. Adds some requirec ancillary permissions for write_usermanagement_teams.

* MM-23832: Adds local API endpoint for getting config.

* MM-23832: If tag value is blank or restrict_sys_admin_write then don't do the permission check.

* MM-23832: nil check for strings prior to dereferencing.

* MM-23832: Fix for config display logic.

* MM-23832: Updates godoc.

* MM-23832: Delays the unrestricted check for parity with other permissions checks if the channel id does not exist.

* MM-23832: Removes tautology.

* MM-23832: Re-adds status code check.

* MM-23832: Adds new permission to edit brand image.

* MM-23832: Exports variable for use by mmctl.

* MM-23832: Initialize exported map for use by mmctl.

* MM-23832: Accept deprecated permissions as valid.

* MM-23832: Adds missing permissions to archive a channel.

* MM-23832: Adds missing permissions for managing team.

* MM-23832: Properly filters config values in patch and update API responses.

* MM-23832: Fixes license viewing and writing permissions.

* MM-23832: Require license to assign 'new system roles'.

* MM-23832: Adds translation keys.

* MM-23832: Updates translation order.

* MM-27529: Splits read_channel_groups into read_public_channel_groups and read_private_channel_groups.

* MM-23832: Prevent read-only permissions from editing site url test parameter.

* MM-23832: Prevent read permissions from sniffing ports and elastic password.

* MM-23832: Adds missing permission required for write user management channels.

* MM-23832: Allows new roles to search for channels.

* MM-23832: Adds ability for system_manager to manage jobs.

* MM-23832: Cluster status access by sysconsole permission, not manage_system.

* MM-23832: Adds 'add_user_to_team' permission to sysconsole write usermanagement teams.

* MM-23832: Fixes lint.

* MM-23832: Test fix.

* MM-23832: Test fix.

Co-authored-by: Catalin Tomai <catalin.tomai@mattermost.com>
Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
2020-08-21 16:49:31 -04:00

380 lines
12 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"database/sql"
"fmt"
"strings"
"github.com/mattermost/gorp"
"github.com/mattermost/mattermost-server/v5/model"
"github.com/mattermost/mattermost-server/v5/store"
"github.com/pkg/errors"
)
type SqlSchemeStore struct {
SqlStore
}
func newSqlSchemeStore(sqlStore SqlStore) store.SchemeStore {
s := &SqlSchemeStore{sqlStore}
for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.Scheme{}, "Schemes").SetKeys(false, "Id")
table.ColMap("Id").SetMaxSize(26)
table.ColMap("Name").SetMaxSize(model.SCHEME_NAME_MAX_LENGTH).SetUnique(true)
table.ColMap("DisplayName").SetMaxSize(model.SCHEME_DISPLAY_NAME_MAX_LENGTH)
table.ColMap("Description").SetMaxSize(model.SCHEME_DESCRIPTION_MAX_LENGTH)
table.ColMap("Scope").SetMaxSize(32)
table.ColMap("DefaultTeamAdminRole").SetMaxSize(64)
table.ColMap("DefaultTeamUserRole").SetMaxSize(64)
table.ColMap("DefaultTeamGuestRole").SetMaxSize(64)
table.ColMap("DefaultChannelAdminRole").SetMaxSize(64)
table.ColMap("DefaultChannelUserRole").SetMaxSize(64)
table.ColMap("DefaultChannelGuestRole").SetMaxSize(64)
}
return s
}
func (s SqlSchemeStore) createIndexesIfNotExists() {
s.CreateIndexIfNotExists("idx_schemes_channel_guest_role", "Schemes", "DefaultChannelGuestRole")
s.CreateIndexIfNotExists("idx_schemes_channel_user_role", "Schemes", "DefaultChannelUserRole")
s.CreateIndexIfNotExists("idx_schemes_channel_admin_role", "Schemes", "DefaultChannelAdminRole")
}
func (s *SqlSchemeStore) Save(scheme *model.Scheme) (*model.Scheme, error) {
if len(scheme.Id) == 0 {
transaction, err := s.GetMaster().Begin()
if err != nil {
return nil, errors.Wrap(err, "begin_transaction")
}
defer finalizeTransaction(transaction)
newScheme, err := s.createScheme(scheme, transaction)
if err != nil {
return nil, err
}
if err := transaction.Commit(); err != nil {
return nil, errors.Wrap(err, "commit_transaction")
}
return newScheme, nil
}
if !scheme.IsValid() {
return nil, store.NewErrInvalidInput("Scheme", "<any>", fmt.Sprintf("%v", scheme))
}
scheme.UpdateAt = model.GetMillis()
rowsChanged, err := s.GetMaster().Update(scheme)
if err != nil {
return nil, errors.Wrap(err, "failed to update Scheme")
}
if rowsChanged != 1 {
return nil, errors.New("no record to update")
}
return scheme, nil
}
func (s *SqlSchemeStore) createScheme(scheme *model.Scheme, transaction *gorp.Transaction) (*model.Scheme, error) {
// Fetch the default system scheme roles to populate default permissions.
defaultRoleNames := []string{model.TEAM_ADMIN_ROLE_ID, model.TEAM_USER_ROLE_ID, model.TEAM_GUEST_ROLE_ID, model.CHANNEL_ADMIN_ROLE_ID, model.CHANNEL_USER_ROLE_ID, model.CHANNEL_GUEST_ROLE_ID}
defaultRoles := make(map[string]*model.Role)
roles, appErr := s.SqlStore.Role().GetByNames(defaultRoleNames)
if appErr != nil {
return nil, appErr
}
for _, role := range roles {
switch role.Name {
case model.TEAM_ADMIN_ROLE_ID:
defaultRoles[model.TEAM_ADMIN_ROLE_ID] = role
case model.TEAM_USER_ROLE_ID:
defaultRoles[model.TEAM_USER_ROLE_ID] = role
case model.TEAM_GUEST_ROLE_ID:
defaultRoles[model.TEAM_GUEST_ROLE_ID] = role
case model.CHANNEL_ADMIN_ROLE_ID:
defaultRoles[model.CHANNEL_ADMIN_ROLE_ID] = role
case model.CHANNEL_USER_ROLE_ID:
defaultRoles[model.CHANNEL_USER_ROLE_ID] = role
case model.CHANNEL_GUEST_ROLE_ID:
defaultRoles[model.CHANNEL_GUEST_ROLE_ID] = role
}
}
if len(defaultRoles) != 6 {
return nil, errors.New("createScheme: unable to retrieve default scheme roles")
}
// Create the appropriate default roles for the scheme.
if scheme.Scope == model.SCHEME_SCOPE_TEAM {
// Team Admin Role
teamAdminRole := &model.Role{
Name: model.NewId(),
DisplayName: fmt.Sprintf("Team Admin Role for Scheme %s", scheme.Name),
Permissions: defaultRoles[model.TEAM_ADMIN_ROLE_ID].Permissions,
SchemeManaged: true,
}
savedRole, err := s.SqlStore.Role().(*SqlRoleStore).createRole(teamAdminRole, transaction)
if err != nil {
return nil, err
}
scheme.DefaultTeamAdminRole = savedRole.Name
// Team User Role
teamUserRole := &model.Role{
Name: model.NewId(),
DisplayName: fmt.Sprintf("Team User Role for Scheme %s", scheme.Name),
Permissions: defaultRoles[model.TEAM_USER_ROLE_ID].Permissions,
SchemeManaged: true,
}
savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(teamUserRole, transaction)
if err != nil {
return nil, err
}
scheme.DefaultTeamUserRole = savedRole.Name
// Team Guest Role
teamGuestRole := &model.Role{
Name: model.NewId(),
DisplayName: fmt.Sprintf("Team Guest Role for Scheme %s", scheme.Name),
Permissions: defaultRoles[model.TEAM_GUEST_ROLE_ID].Permissions,
SchemeManaged: true,
}
savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(teamGuestRole, transaction)
if err != nil {
return nil, err
}
scheme.DefaultTeamGuestRole = savedRole.Name
}
if scheme.Scope == model.SCHEME_SCOPE_TEAM || scheme.Scope == model.SCHEME_SCOPE_CHANNEL {
// Channel Admin Role
channelAdminRole := &model.Role{
Name: model.NewId(),
DisplayName: fmt.Sprintf("Channel Admin Role for Scheme %s", scheme.Name),
Permissions: defaultRoles[model.CHANNEL_ADMIN_ROLE_ID].Permissions,
SchemeManaged: true,
}
if scheme.Scope == model.SCHEME_SCOPE_CHANNEL {
channelAdminRole.Permissions = []string{}
}
savedRole, err := s.SqlStore.Role().(*SqlRoleStore).createRole(channelAdminRole, transaction)
if err != nil {
return nil, err
}
scheme.DefaultChannelAdminRole = savedRole.Name
// Channel User Role
channelUserRole := &model.Role{
Name: model.NewId(),
DisplayName: fmt.Sprintf("Channel User Role for Scheme %s", scheme.Name),
Permissions: defaultRoles[model.CHANNEL_USER_ROLE_ID].Permissions,
SchemeManaged: true,
}
if scheme.Scope == model.SCHEME_SCOPE_CHANNEL {
channelUserRole.Permissions = filterModerated(channelUserRole.Permissions)
}
savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(channelUserRole, transaction)
if err != nil {
return nil, err
}
scheme.DefaultChannelUserRole = savedRole.Name
// Channel Guest Role
channelGuestRole := &model.Role{
Name: model.NewId(),
DisplayName: fmt.Sprintf("Channel Guest Role for Scheme %s", scheme.Name),
Permissions: defaultRoles[model.CHANNEL_GUEST_ROLE_ID].Permissions,
SchemeManaged: true,
}
if scheme.Scope == model.SCHEME_SCOPE_CHANNEL {
channelGuestRole.Permissions = filterModerated(channelGuestRole.Permissions)
}
savedRole, err = s.SqlStore.Role().(*SqlRoleStore).createRole(channelGuestRole, transaction)
if err != nil {
return nil, err
}
scheme.DefaultChannelGuestRole = savedRole.Name
}
scheme.Id = model.NewId()
if len(scheme.Name) == 0 {
scheme.Name = model.NewId()
}
scheme.CreateAt = model.GetMillis()
scheme.UpdateAt = scheme.CreateAt
// Validate the scheme
if !scheme.IsValidForCreate() {
return nil, store.NewErrInvalidInput("Scheme", "<any>", fmt.Sprintf("%v", scheme))
}
if err := transaction.Insert(scheme); err != nil {
return nil, errors.Wrap(err, "failed to save Scheme")
}
return scheme, nil
}
func filterModerated(permissions []string) []string {
filteredPermissions := []string{}
for _, perm := range permissions {
if _, ok := model.ChannelModeratedPermissionsMap[perm]; ok {
filteredPermissions = append(filteredPermissions, perm)
}
}
return filteredPermissions
}
func (s *SqlSchemeStore) Get(schemeId string) (*model.Scheme, error) {
var scheme model.Scheme
if err := s.GetReplica().SelectOne(&scheme, "SELECT * from Schemes WHERE Id = :Id", map[string]interface{}{"Id": schemeId}); err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("Scheme", fmt.Sprintf("schemeId=%s", schemeId))
}
return nil, errors.Wrapf(err, "failed to get Scheme with schemeId=%s", schemeId)
}
return &scheme, nil
}
func (s *SqlSchemeStore) GetByName(schemeName string) (*model.Scheme, error) {
var scheme model.Scheme
if err := s.GetReplica().SelectOne(&scheme, "SELECT * from Schemes WHERE Name = :Name", map[string]interface{}{"Name": schemeName}); err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("Scheme", fmt.Sprintf("schemeName=%s", schemeName))
}
return nil, errors.Wrapf(err, "failed to get Scheme with schemeName=%s", schemeName)
}
return &scheme, nil
}
func (s *SqlSchemeStore) Delete(schemeId string) (*model.Scheme, error) {
// Get the scheme
var scheme model.Scheme
if err := s.GetReplica().SelectOne(&scheme, "SELECT * from Schemes WHERE Id = :Id", map[string]interface{}{"Id": schemeId}); err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("Scheme", fmt.Sprintf("schemeId=%s", schemeId))
}
return nil, errors.Wrapf(err, "failed to get Scheme with schemeId=%s", schemeId)
}
// Update any teams or channels using this scheme to the default scheme.
if scheme.Scope == model.SCHEME_SCOPE_TEAM {
if _, err := s.GetMaster().Exec("UPDATE Teams SET SchemeId = '' WHERE SchemeId = :SchemeId", map[string]interface{}{"SchemeId": schemeId}); err != nil {
return nil, errors.Wrapf(err, "failed to update Teams with schemeId=%s", schemeId)
}
s.Team().ClearCaches()
} else if scheme.Scope == model.SCHEME_SCOPE_CHANNEL {
if _, err := s.GetMaster().Exec("UPDATE Channels SET SchemeId = '' WHERE SchemeId = :SchemeId", map[string]interface{}{"SchemeId": schemeId}); err != nil {
return nil, errors.Wrapf(err, "failed to update Channels with schemeId=%s", schemeId)
}
}
// Blow away the channel caches.
s.Channel().ClearCaches()
// Delete the roles belonging to the scheme.
roleNames := []string{scheme.DefaultChannelGuestRole, scheme.DefaultChannelUserRole, scheme.DefaultChannelAdminRole}
if scheme.Scope == model.SCHEME_SCOPE_TEAM {
roleNames = append(roleNames, scheme.DefaultTeamGuestRole, scheme.DefaultTeamUserRole, scheme.DefaultTeamAdminRole)
}
var inQueryList []string
queryArgs := make(map[string]interface{})
for i, roleId := range roleNames {
inQueryList = append(inQueryList, fmt.Sprintf(":RoleName%v", i))
queryArgs[fmt.Sprintf("RoleName%v", i)] = roleId
}
inQuery := strings.Join(inQueryList, ", ")
time := model.GetMillis()
queryArgs["UpdateAt"] = time
queryArgs["DeleteAt"] = time
if _, err := s.GetMaster().Exec("UPDATE Roles SET UpdateAt = :UpdateAt, DeleteAt = :DeleteAt WHERE Name IN ("+inQuery+")", queryArgs); err != nil {
return nil, errors.Wrapf(err, "failed to update Roles with name in (%s)", inQuery)
}
// Delete the scheme itself.
scheme.UpdateAt = time
scheme.DeleteAt = time
rowsChanged, err := s.GetMaster().Update(&scheme)
if err != nil {
return nil, errors.Wrapf(err, "failed to update Scheme with schemeId=%s", schemeId)
}
if rowsChanged != 1 {
return nil, errors.New("no record to update")
}
return &scheme, nil
}
func (s *SqlSchemeStore) GetAllPage(scope string, offset int, limit int) ([]*model.Scheme, error) {
var schemes []*model.Scheme
scopeClause := ""
if len(scope) > 0 {
scopeClause = " AND Scope=:Scope "
}
if _, err := s.GetReplica().Select(&schemes, "SELECT * from Schemes WHERE DeleteAt = 0 "+scopeClause+" ORDER BY CreateAt DESC LIMIT :Limit OFFSET :Offset", map[string]interface{}{"Limit": limit, "Offset": offset, "Scope": scope}); err != nil {
return nil, errors.Wrapf(err, "failed to get Schemes")
}
return schemes, nil
}
func (s *SqlSchemeStore) PermanentDeleteAll() error {
if _, err := s.GetMaster().Exec("DELETE from Schemes"); err != nil {
return errors.Wrap(err, "failed to delete Schemes")
}
return nil
}
func (s *SqlSchemeStore) CountByScope(scope string) (int64, error) {
count, err := s.GetReplica().SelectInt("SELECT count(*) FROM Schemes WHERE Scope = :Scope AND DeleteAt = 0", map[string]interface{}{"Scope": scope})
if err != nil {
return int64(0), errors.Wrap(err, "failed to count Schemes by scope")
}
return count, nil
}
func (s *SqlSchemeStore) CountWithoutPermission(schemeScope, permissionID string, roleScope model.RoleScope, roleType model.RoleType) (int64, error) {
joinCol := fmt.Sprintf("Default%s%sRole", roleScope, roleType)
query := fmt.Sprintf(`
SELECT
count(*)
FROM Schemes
JOIN Roles ON Roles.Name = Schemes.%s
WHERE
Schemes.DeleteAt = 0 AND
Schemes.Scope = '%s' AND
Roles.Permissions NOT LIKE '%%%s%%'
`, joinCol, schemeScope, permissionID)
count, err := s.GetReplica().SelectInt(query)
if err != nil {
return int64(0), errors.Wrap(err, "failed to count Schemes without permission")
}
return count, nil
}