MM-63200: unrestricted local admin (#30295)

* use SessionHasPermissionToCheckRestrictedAdmin

* allow unrestricted config edits from localmode

* check model.PermissionManageSystem for getLatestVersion

* simplify/clarify RequestTrialLicense semantics

* rename for clarity

* whitespace from linter

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Jesse Hallam
2025-04-10 16:22:03 -03:00
коммит произвёл GitHub
родитель ca9fd45408
Коммит 42274b9eee
11 изменённых файлов: 142 добавлений и 117 удалений

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

@@ -21,6 +21,20 @@ func (a *App) SessionHasPermissionTo(session model.Session, permission *model.Pe
return a.RolesGrantPermission(session.GetUserRoles(), permission.Id)
}
// SessionHasPermissionToAndNotRestrictedAdmin is a variant of [App.SessionHasPermissionTo] that
// denies access to restricted system admins. Note that a local session is always unrestricted.
func (a *App) SessionHasPermissionToAndNotRestrictedAdmin(session model.Session, permission *model.Permission) bool {
if session.IsUnrestricted() {
return true
}
if *a.Config().ExperimentalSettings.RestrictSystemAdmin {
return false
}
return a.RolesGrantPermission(session.GetUserRoles(), permission.Id)
}
func (a *App) SessionHasPermissionToAny(session model.Session, permissions []*model.Permission) bool {
for _, perm := range permissions {
if a.SessionHasPermissionTo(session, perm) {

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

@@ -21,6 +21,102 @@ import (
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
)
func TestSessionHasPermissionTo(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
localSession := model.Session{
UserId: th.BasicUser.Id,
Roles: model.SystemUserRoleId,
Local: true,
}
adminSession := model.Session{
UserId: th.SystemAdminUser.Id,
Roles: model.SystemAdminRoleId,
}
session := model.Session{
UserId: th.BasicUser.Id,
Roles: model.SystemUserRoleId,
}
t.Run("basic user cannot manage system", func(t *testing.T) {
require.False(t, th.App.SessionHasPermissionTo(session, model.PermissionManageSystem))
})
t.Run("basic user generally has no global permissions", func(t *testing.T) {
require.False(t, th.App.SessionHasPermissionTo(session, model.PermissionReadPublicChannel))
})
t.Run("system admin can manage system", func(t *testing.T) {
require.True(t, th.App.SessionHasPermissionTo(adminSession, model.PermissionManageSystem))
})
t.Run("unrestricted session has all permissions", func(t *testing.T) {
require.True(t, th.App.SessionHasPermissionTo(localSession, model.PermissionManageSystem))
require.True(t, th.App.SessionHasPermissionTo(localSession, model.PermissionCreateBot))
require.True(t, th.App.SessionHasPermissionTo(localSession, model.PermissionReadPublicChannel))
})
}
func TestSessionHasPermissionToAndNotRestrictedAdmin(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
localSession := model.Session{
UserId: th.BasicUser.Id,
Roles: model.SystemUserRoleId,
Local: true,
}
adminSession := model.Session{
UserId: th.SystemAdminUser.Id,
Roles: model.SystemAdminRoleId,
}
session := model.Session{
UserId: th.BasicUser.Id,
Roles: model.SystemUserRoleId,
}
t.Run("basic user cannot manage system", func(t *testing.T) {
require.False(t, th.App.SessionHasPermissionToAndNotRestrictedAdmin(session, model.PermissionManageSystem))
})
t.Run("allow system admin when not restricted", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ExperimentalSettings.RestrictSystemAdmin = false
})
require.True(t, th.App.SessionHasPermissionToAndNotRestrictedAdmin(adminSession, model.PermissionManageSystem))
})
t.Run("reject system admin when restricted", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ExperimentalSettings.RestrictSystemAdmin = true
})
require.False(t, th.App.SessionHasPermissionToAndNotRestrictedAdmin(adminSession, model.PermissionManageSystem))
})
t.Run("always allow unrestricted session", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ExperimentalSettings.RestrictSystemAdmin = false
})
require.True(t, th.App.SessionHasPermissionToAndNotRestrictedAdmin(localSession, model.PermissionManageSystem))
require.True(t, th.App.SessionHasPermissionToAndNotRestrictedAdmin(localSession, model.PermissionCreateBot))
require.True(t, th.App.SessionHasPermissionToAndNotRestrictedAdmin(localSession, model.PermissionReadPublicChannel))
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.ExperimentalSettings.RestrictSystemAdmin = true
})
require.True(t, th.App.SessionHasPermissionToAndNotRestrictedAdmin(localSession, model.PermissionManageSystem))
require.True(t, th.App.SessionHasPermissionToAndNotRestrictedAdmin(localSession, model.PermissionCreateBot))
require.True(t, th.App.SessionHasPermissionToAndNotRestrictedAdmin(localSession, model.PermissionReadPublicChannel))
})
}
func TestCheckIfRolesGrantPermission(t *testing.T) {
th := Setup(t)
defer th.TearDown()

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

@@ -18,10 +18,6 @@ func (ch *Channels) License() *model.License {
}
func (ch *Channels) RequestTrialLicenseWithExtraFields(requesterID string, trialRequest *model.TrialLicenseRequest) *model.AppError {
if *ch.srv.platform.Config().ExperimentalSettings.RestrictSystemAdmin {
return model.NewAppError("RequestTrialLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden)
}
requester, err := ch.srv.userService.GetUser(requesterID)
if err != nil {
var nfErr *store.ErrNotFound
@@ -63,10 +59,6 @@ func (ch *Channels) RequestTrialLicenseWithExtraFields(requesterID string, trial
// Deprecated: Use RequestTrialLicenseWithExtraFields instead. This function remains to support the Plugin API.
func (ch *Channels) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError {
if *ch.srv.platform.Config().ExperimentalSettings.RestrictSystemAdmin {
return model.NewAppError("RequestTrialLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden)
}
if !termsAccepted {
return model.NewAppError("RequestTrialLicense", "api.license.request-trial.bad-request.terms-not-accepted", nil, "", http.StatusBadRequest)
}

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

@@ -1354,6 +1354,9 @@ func (api *PluginAPI) PublishPluginClusterEvent(ev model.PluginClusterEvent,
// RequestTrialLicense requests a trial license and installs it in the server
func (api *PluginAPI) RequestTrialLicense(requesterID string, users int, termsAccepted bool, receiveEmailsAccepted bool) *model.AppError {
// Normally, plugins are unrestricted in their abilities, but to maintain backwards compatbilibity with plugins
// that were unaware of the nuances of ExperimentalSettings.RestrictSystemAdmin, we restrict the trial license
// unconditionally.
if *api.app.Config().ExperimentalSettings.RestrictSystemAdmin {
return model.NewAppError("RequestTrialLicense", "api.restricted_system_admin", nil, "", http.StatusForbidden)
}