mirror of
https://github.com/mattermost/mattermost.git
synced 2026-05-28 04:35:04 -04:00
* MM-68762: Add Postgres migrations for discoverable private channels
Three online-safe migrations introduce the schema that supports the
Discoverable Private Channels feature (PRs 2-5 of MM-68430 will land
behind it):
- 000175 adds Channels.Discoverable BOOLEAN NOT NULL DEFAULT FALSE.
Metadata-only on Postgres >= 11; no table rewrite.
- 000176 creates a partial index on
(TeamId) WHERE Discoverable AND Type='P' AND DeleteAt=0
using CREATE INDEX CONCURRENTLY (-- morph:nontransactional) so the
build never blocks writes on the populated Channels table.
- 000177 creates the ChannelJoinRequests table with three indexes, the
important one being the partial unique index on (ChannelId, UserId)
WHERE Status = 'pending'. That keeps the full audit history intact
while still enforcing at-most-one active pending request per
(channel, user).
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Add FeatureFlagDiscoverableChannels (default false)
Gates the per-channel Discoverable toggle and the channel-join-request
flow. Default-OFF so all PRs in the MM-68430 series can land on master
without exposing partial UX.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Add Discoverable + ChannelJoinRequest models
- Channel gains a Discoverable bool, ChannelPatch a *bool, both serialized
as 'discoverable'. Patch() applies it, Auditable() logs it, and IsValid()
rejects Discoverable=true on any non-private channel so a misconfigured
patch can never produce a public discoverable channel.
- New ChannelJoinRequest type captures the per-row state of a non-member's
request: pending -> approved | denied | withdrawn. Rows are append-only
with reviewer and timestamps so the table is also the audit trail.
IsValid() enforces:
* recognized status,
* Message and DenialReason rune limits,
* DenialReason only on denied rows (no orphan reasons),
* reviewer + reviewed_at present for any terminal review (approved /
denied) but not for self-service withdrawal.
- Two new WebSocket event constants -- channel_join_request_created and
channel_join_request_updated -- that later PRs broadcast on the admin
queue and the requester's My Pending Requests panel.
Unit tests cover Patch(), the new IsValid() rule on Discoverable, the
PreSave/PreUpdate timestamp behavior on ChannelJoinRequest, and every
IsValid branch including the reviewer-required-on-review invariant.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Add discoverable-channel permissions
Two new channel-scoped permissions, each independently rebindable from
the System Console:
- manage_private_channel_discoverability gates the per-channel toggle so
admins can restrict who can flip discoverability without also handing
out manage_private_channel_properties.
- manage_channel_join_requests gates the queue list / approve / deny /
count endpoints (added in PR 2).
Both are added to the channel_admin role bootstrap so new deployments
get them by default, and a new permissions migration
(add_discoverable_channel_permissions) grants them to channel_admin,
team_admin and system_admin scheme roles on existing deployments.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Add ChannelJoinRequestStore and wire Discoverable into channel store
- channelSliceColumns / channelToSlice / updateChannelT now include the
new Discoverable column so Save() and Update() round-trip the field.
Existing select paths inherit the column automatically because every
read goes through channelSliceColumns.
- New ChannelJoinRequestStore interface and SQL implementation:
Save / Get / GetPendingForChannelAndUser / GetForChannel / GetForUser
/ Update / CountPending. Save translates the
idx_channeljoinrequests_pending_unique partial unique index violation
into store.ErrConflict so the app layer (PR 2) can return 409 without
re-parsing pq errors.
- Storetest suite at storetest/channel_join_request_store.go is invoked
from sqlstore via the existing StoreTest harness; covers insert /
partial-unique conflict / re-insert after withdrawal / NotFound /
status filtering / pagination with TotalCount / Update / CountPending.
- Mocks and retrylayer / timerlayer are regenerated via make store-mocks
and go generate ./channels/store -- no hand-written generator output.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Add TS types for Discoverable channels + join requests
webapp/platform/types:
- Channel.discoverable?: boolean alongside existing policy_enforced /
policy_is_active so the web client sees the same wire shape the server
emits.
- ChannelJoinRequest, ChannelJoinRequestStatus, ChannelJoinRequestList,
GetChannelJoinRequestsOptions for the API contract surfaced in PR 2.
webapp/platform/client:
- WebSocketEvents enum gains ChannelJoinRequestCreated and
ChannelJoinRequestUpdated so PR 3 can hang WS handlers off them
without redeclaring constants.
These are model-only updates with no UI consumer yet; PR 3 introduces
the toggle, request flow, and admin queue surfaces.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Split ChannelJoinRequests indexes into concurrent migrations
The mattermost-govet concurrentIndex lint check enforces CREATE INDEX
CONCURRENTLY on every CREATE INDEX statement, even on an empty
freshly-created table where it would be a no-op. The original 000177
file inlined three CREATE INDEX statements; that failed check-style.
Mirror the convention used by 000166_create_views +
000167_create_views_channel_id_delete_at_index: keep the CREATE TABLE
in its own (transactional) file, and move each index into a separate
nontransactional file that runs CREATE INDEX CONCURRENTLY. Verified
locally against Postgres 15 that all four new migrations apply in
order and the storetest suite (partial unique constraint + paged
list + count) still passes.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Wire new permission migration into test fixtures
Two CI test surfaces missed when the channel_admin role and the
permission-migration list gained the new
manage_private_channel_discoverability and manage_channel_join_requests
entries:
- testlib/store.go: the shared mocked SystemStore used by
SetupWithStoreMock / SetupEnterpriseWithStoreMock needs an explicit
GetByName expectation for every migration key (because the mock
panics on unexpected calls). Add the new
MigrationKeyAddDiscoverableChannelPermissions key so
TestCreateOrUpdateAccessControlPolicy, the elasticsearch
aggregation_job_test, and every other mock-store test stop panicking
on server bootstrap.
- cmd/mmctl/commands/permissions_test.go: TestResetPermissionsCmd
hard-codes the channel_admin default permission list and expects
PatchRole to be called with exactly that slice. Extend the expected
slice with the two new permission ids so the mmctl reset path stays
in sync with the role bootstrap.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Register new idx_channels_discoverable_team in TestGetSchemaDefinition
The schema-dump test asserts an exact index count and definition map
for the channels table. Migration 000176 added
idx_channels_discoverable_team — a partial btree on (teamid) gated by
discoverable=true AND type='P' AND deleteat=0. Bump the expected count
from 12 to 13 and add the index's CREATE INDEX definition as produced
by pg_indexes (note: type is cast to channel_type, the existing
domain). Verified locally against Postgres 15.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Fix golangci-lint findings in ChannelJoinRequest store
Two golangci-lint findings on the freshly-added files:
- sqlstore/channel_join_request_store.go:133 (modernize): collapse the
'if page < 0 { page = 0 }' clamp into max(opts.Page, 0).
- storetest/channel_join_request_store.go:243 (govet shadow): the
inner Save loop redeclared err with :=, shadowing the outer err
captured from the first CountPending call. Switch to plain
assignment so the same err is reused.
Verified locally with golangci-lint v2.11.4 across public/...,
channels/app/..., channels/store/..., channels/testlib/... and
cmd/mmctl/commands/... — 0 issues.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Sync channel_admin bootstrap with TestDoAdvancedPermissionsMigration
app_test.go pins the exact list of permissions the channel_admin role
is expected to hold after DoAdvancedPermissionsMigration completes.
The role bootstrap in role.go grew two entries
(manage_private_channel_discoverability and manage_channel_join_requests),
so the test's expected slice needs the same two entries appended in
the same order, otherwise assert.Equal fails on slice ordering.
This is the same class of fix as the mmctl/permissions_test.go change
in a previous commit -- two parallel test fixtures encode the
channel_admin defaults and have to be updated in lockstep with the
bootstrap.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Add English translations for new model error keys
12 keys were emitted by the new Discoverable + ChannelJoinRequest
validation paths but had no en.json entry, which trips i18n-check on
CI. Add the missing entries with one-line English copy that mirrors
adjacent model errors (Invalid <field>., Create at must be a valid
time., etc.). The new entries are:
- model.channel.is_valid.discoverable.app_error
- model.channel_join_request.is_valid.channel_id.app_error
- model.channel_join_request.is_valid.create_at.app_error
- model.channel_join_request.is_valid.denial_reason.app_error
- model.channel_join_request.is_valid.denial_reason_status.app_error
- model.channel_join_request.is_valid.id.app_error
- model.channel_join_request.is_valid.message.app_error
- model.channel_join_request.is_valid.reviewed_by.app_error
- model.channel_join_request.is_valid.reviewer.app_error
- model.channel_join_request.is_valid.status.app_error
- model.channel_join_request.is_valid.update_at.app_error
- model.channel_join_request.is_valid.user_id.app_error
Generated through 'make i18n-extract'; verified clean with
'make i18n-check'. Per the workspace rule, only en.json was modified --
no other locale files.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Address CodeRabbit review: stable pagination + redact denial reason from audit log
Two production-code findings from CodeRabbit on the freshly-added
ChannelJoinRequest server code:
- sqlstore/channel_join_request_store.go (GetForChannel / GetForUser):
OrderBy("CreateAt DESC") alone is unstable when two rows share a
millisecond (NewId is monotonic-ish but CreateAt is millisecond
resolution), so offset paging could duplicate or skip rows between
pages. Add Id DESC as a deterministic tie-breaker on both list
queries.
- model/channel_join_request.Auditable: the denial reason is admin-typed
free text and could carry sensitive content. Mirror the existing
has_message pattern by emitting has_denial_reason as a boolean
presence flag instead of the raw value. Reviewer id, review timestamp,
and status are still logged, so the audit trail keeps every piece
needed for compliance review.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Tighten model tests per CodeRabbit review
Two test-only findings from CodeRabbit:
- TestChannelJoinRequestPreUpdateAdvancesUpdateAt previously asserted
GreaterOrEqual(r.UpdateAt, originalCreate). Because validRequest
initialises UpdateAt to GetMillis() (same call site as CreateAt), a
no-op PreUpdate would still pass that check. Seed r.UpdateAt = 1
before calling PreUpdate() and assert Greater(r.UpdateAt, int64(1))
so any regression that drops the GetMillis assignment fails the test.
- TestChannelIsValidDiscoverable did not cover ChannelTypeGroup. Add the
case alongside ChannelTypeOpen and ChannelTypeDirect so the contract
that 'only ChannelTypePrivate accepts Discoverable=true' is fully
pinned across all four channel types.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
* MM-68762: Mock ChannelJoinRequest accessor in retrylayer test
retrylayer_test.go's genStore() helper mocks every Store() accessor
because retrylayer.New() wraps the entire surface. The new
ChannelJoinRequest() method I added on Store was missing from the
mock, so TestRetry/on_regular_error_should_not_retry panicked with
'Unexpected Method Call ChannelJoinRequest()' on Postgres shard 0.
Add the mock alongside the other accessors. No production code
change.
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Ibrahim Serdar Acikgoz <isacikgoz@users.noreply.github.com>
1243 lines
41 KiB
Go
1243 lines
41 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/mattermost/mattermost/server/public/utils/timeutils"
|
|
)
|
|
|
|
// SysconsoleAncillaryPermissions maps the non-sysconsole permissions required by each sysconsole view.
|
|
var SysconsoleAncillaryPermissions map[string][]*Permission
|
|
var SystemManagerDefaultPermissions []string
|
|
var SystemUserManagerDefaultPermissions []string
|
|
var SystemReadOnlyAdminDefaultPermissions []string
|
|
var SystemCustomGroupAdminDefaultPermissions []string
|
|
var SharedChannelManagerDefaultPermissions []string
|
|
|
|
var BuiltInSchemeManagedRoleIDs []string
|
|
|
|
var NewSystemRoleIDs []string
|
|
|
|
func init() {
|
|
NewSystemRoleIDs = []string{
|
|
SystemUserManagerRoleId,
|
|
SystemReadOnlyAdminRoleId,
|
|
SystemManagerRoleId,
|
|
SharedChannelManagerRoleId,
|
|
}
|
|
|
|
BuiltInSchemeManagedRoleIDs = append([]string{
|
|
SystemGuestRoleId,
|
|
SystemUserRoleId,
|
|
SystemAdminRoleId,
|
|
SystemPostAllRoleId,
|
|
SystemPostAllPublicRoleId,
|
|
SystemUserAccessTokenRoleId,
|
|
|
|
TeamGuestRoleId,
|
|
TeamUserRoleId,
|
|
TeamAdminRoleId,
|
|
TeamPostAllRoleId,
|
|
TeamPostAllPublicRoleId,
|
|
|
|
ChannelGuestRoleId,
|
|
ChannelUserRoleId,
|
|
ChannelAdminRoleId,
|
|
|
|
CustomGroupUserRoleId,
|
|
|
|
PlaybookAdminRoleId,
|
|
PlaybookMemberRoleId,
|
|
RunAdminRoleId,
|
|
RunMemberRoleId,
|
|
}, NewSystemRoleIDs...)
|
|
|
|
// When updating the values here, the values in mattermost-redux must also be updated.
|
|
SysconsoleAncillaryPermissions = map[string][]*Permission{
|
|
PermissionSysconsoleReadAboutEditionAndLicense.Id: {
|
|
PermissionReadLicenseInformation,
|
|
},
|
|
PermissionSysconsoleWriteAboutEditionAndLicense.Id: {
|
|
PermissionManageLicenseInformation,
|
|
},
|
|
PermissionSysconsoleReadUserManagementChannels.Id: {
|
|
PermissionReadPublicChannel,
|
|
PermissionReadChannel,
|
|
PermissionReadPublicChannelGroups,
|
|
PermissionReadPrivateChannelGroups,
|
|
},
|
|
PermissionSysconsoleReadUserManagementUsers.Id: {
|
|
PermissionReadOtherUsersTeams,
|
|
},
|
|
PermissionSysconsoleReadUserManagementTeams.Id: {
|
|
PermissionListPrivateTeams,
|
|
PermissionListPublicTeams,
|
|
PermissionViewTeam,
|
|
},
|
|
PermissionSysconsoleReadEnvironmentElasticsearch.Id: {
|
|
PermissionReadElasticsearchPostIndexingJob,
|
|
PermissionReadElasticsearchPostAggregationJob,
|
|
},
|
|
PermissionSysconsoleWriteEnvironmentWebServer.Id: {
|
|
PermissionTestSiteURL,
|
|
PermissionReloadConfig,
|
|
PermissionInvalidateCaches,
|
|
},
|
|
PermissionSysconsoleWriteEnvironmentDatabase.Id: {
|
|
PermissionRecycleDatabaseConnections,
|
|
},
|
|
PermissionSysconsoleWriteEnvironmentElasticsearch.Id: {
|
|
PermissionTestElasticsearch,
|
|
PermissionCreateElasticsearchPostIndexingJob,
|
|
PermissionManageElasticsearchPostIndexingJob,
|
|
PermissionCreateElasticsearchPostAggregationJob,
|
|
PermissionManageElasticsearchPostAggregationJob,
|
|
PermissionPurgeElasticsearchIndexes,
|
|
},
|
|
PermissionSysconsoleWriteEnvironmentFileStorage.Id: {
|
|
PermissionTestS3,
|
|
},
|
|
PermissionSysconsoleWriteEnvironmentSMTP.Id: {
|
|
PermissionTestEmail,
|
|
},
|
|
PermissionSysconsoleReadReportingServerLogs.Id: {
|
|
PermissionGetLogs,
|
|
},
|
|
PermissionSysconsoleReadReportingSiteStatistics.Id: {
|
|
PermissionGetAnalytics,
|
|
},
|
|
PermissionSysconsoleReadReportingTeamStatistics.Id: {
|
|
PermissionGetAnalytics,
|
|
},
|
|
PermissionSysconsoleWriteUserManagementUsers.Id: {
|
|
PermissionEditOtherUsers,
|
|
PermissionDemoteToGuest,
|
|
PermissionPromoteGuest,
|
|
},
|
|
PermissionSysconsoleWriteUserManagementChannels.Id: {
|
|
PermissionManagePublicChannelProperties,
|
|
PermissionManagePrivateChannelProperties,
|
|
PermissionManagePublicChannelAutoTranslation,
|
|
PermissionManagePrivateChannelAutoTranslation,
|
|
PermissionManagePrivateChannelMembers,
|
|
PermissionManagePublicChannelMembers,
|
|
PermissionDeletePrivateChannel,
|
|
PermissionDeletePublicChannel,
|
|
PermissionManageChannelRoles,
|
|
PermissionConvertPublicChannelToPrivate,
|
|
PermissionConvertPrivateChannelToPublic,
|
|
},
|
|
PermissionSysconsoleWriteUserManagementTeams.Id: {
|
|
PermissionManageTeam,
|
|
PermissionManageTeamRoles,
|
|
PermissionRemoveUserFromTeam,
|
|
PermissionJoinPrivateTeams,
|
|
PermissionJoinPublicTeams,
|
|
PermissionAddUserToTeam,
|
|
},
|
|
PermissionSysconsoleWriteUserManagementGroups.Id: {
|
|
PermissionManagePrivateChannelMembers,
|
|
PermissionManagePublicChannelMembers,
|
|
PermissionConvertPublicChannelToPrivate,
|
|
PermissionConvertPrivateChannelToPublic,
|
|
},
|
|
PermissionSysconsoleWriteSiteCustomization.Id: {
|
|
PermissionEditBrand,
|
|
},
|
|
PermissionSysconsoleWriteComplianceDataRetentionPolicy.Id: {
|
|
PermissionCreateDataRetentionJob,
|
|
PermissionManageDataRetentionJob,
|
|
},
|
|
PermissionSysconsoleReadComplianceDataRetentionPolicy.Id: {
|
|
PermissionReadDataRetentionJob,
|
|
},
|
|
PermissionSysconsoleWriteComplianceComplianceExport.Id: {
|
|
PermissionCreateComplianceExportJob,
|
|
PermissionManageComplianceExportJob,
|
|
PermissionDownloadComplianceExportResult,
|
|
},
|
|
PermissionSysconsoleReadComplianceComplianceExport.Id: {
|
|
PermissionReadComplianceExportJob,
|
|
PermissionDownloadComplianceExportResult,
|
|
},
|
|
PermissionSysconsoleReadComplianceComplianceMonitoring.Id: {
|
|
PermissionReadAudits,
|
|
},
|
|
PermissionSysconsoleWriteAuthenticationLdap.Id: {
|
|
PermissionCreateLdapSyncJob,
|
|
PermissionManageLdapSyncJob,
|
|
PermissionAddLdapPublicCert,
|
|
PermissionRemoveLdapPublicCert,
|
|
PermissionAddLdapPrivateCert,
|
|
PermissionRemoveLdapPrivateCert,
|
|
},
|
|
PermissionSysconsoleReadAuthenticationLdap.Id: {
|
|
PermissionTestLdap,
|
|
PermissionReadLdapSyncJob,
|
|
},
|
|
PermissionSysconsoleWriteAuthenticationEmail.Id: {
|
|
PermissionInvalidateEmailInvite,
|
|
},
|
|
PermissionSysconsoleWriteAuthenticationSaml.Id: {
|
|
PermissionGetSamlMetadataFromIdp,
|
|
PermissionAddSamlPublicCert,
|
|
PermissionAddSamlPrivateCert,
|
|
PermissionAddSamlIdpCert,
|
|
PermissionRemoveSamlPublicCert,
|
|
PermissionRemoveSamlPrivateCert,
|
|
PermissionRemoveSamlIdpCert,
|
|
PermissionGetSamlCertStatus,
|
|
},
|
|
}
|
|
|
|
SystemUserManagerDefaultPermissions = []string{
|
|
PermissionSysconsoleReadUserManagementGroups.Id,
|
|
PermissionSysconsoleReadUserManagementTeams.Id,
|
|
PermissionSysconsoleReadUserManagementChannels.Id,
|
|
PermissionSysconsoleReadUserManagementPermissions.Id,
|
|
PermissionSysconsoleWriteUserManagementGroups.Id,
|
|
PermissionSysconsoleWriteUserManagementTeams.Id,
|
|
PermissionSysconsoleWriteUserManagementChannels.Id,
|
|
PermissionSysconsoleReadAuthenticationSignup.Id,
|
|
PermissionSysconsoleReadAuthenticationEmail.Id,
|
|
PermissionSysconsoleReadAuthenticationPassword.Id,
|
|
PermissionSysconsoleReadAuthenticationMfa.Id,
|
|
PermissionSysconsoleReadAuthenticationLdap.Id,
|
|
PermissionSysconsoleReadAuthenticationSaml.Id,
|
|
PermissionSysconsoleReadAuthenticationOpenid.Id,
|
|
PermissionSysconsoleReadAuthenticationGuestAccess.Id,
|
|
}
|
|
|
|
SystemReadOnlyAdminDefaultPermissions = []string{
|
|
PermissionSysconsoleReadAboutEditionAndLicense.Id,
|
|
PermissionSysconsoleReadReportingSiteStatistics.Id,
|
|
PermissionSysconsoleReadReportingTeamStatistics.Id,
|
|
PermissionSysconsoleReadReportingServerLogs.Id,
|
|
PermissionSysconsoleReadUserManagementUsers.Id,
|
|
PermissionSysconsoleReadUserManagementGroups.Id,
|
|
PermissionSysconsoleReadUserManagementTeams.Id,
|
|
PermissionSysconsoleReadUserManagementChannels.Id,
|
|
PermissionSysconsoleReadUserManagementPermissions.Id,
|
|
PermissionSysconsoleReadEnvironmentWebServer.Id,
|
|
PermissionSysconsoleReadEnvironmentDatabase.Id,
|
|
PermissionSysconsoleReadEnvironmentElasticsearch.Id,
|
|
PermissionSysconsoleReadEnvironmentFileStorage.Id,
|
|
PermissionSysconsoleReadEnvironmentImageProxy.Id,
|
|
PermissionSysconsoleReadEnvironmentSMTP.Id,
|
|
PermissionSysconsoleReadEnvironmentPushNotificationServer.Id,
|
|
PermissionSysconsoleReadEnvironmentHighAvailability.Id,
|
|
PermissionSysconsoleReadEnvironmentRateLimiting.Id,
|
|
PermissionSysconsoleReadEnvironmentLogging.Id,
|
|
PermissionSysconsoleReadEnvironmentSessionLengths.Id,
|
|
PermissionSysconsoleReadEnvironmentPerformanceMonitoring.Id,
|
|
PermissionSysconsoleReadEnvironmentDeveloper.Id,
|
|
PermissionSysconsoleReadSiteCustomization.Id,
|
|
PermissionSysconsoleReadSiteLocalization.Id,
|
|
PermissionSysconsoleReadSiteUsersAndTeams.Id,
|
|
PermissionSysconsoleReadSiteNotifications.Id,
|
|
PermissionSysconsoleReadSiteAnnouncementBanner.Id,
|
|
PermissionSysconsoleReadSiteEmoji.Id,
|
|
PermissionSysconsoleReadSitePosts.Id,
|
|
PermissionSysconsoleReadSiteFileSharingAndDownloads.Id,
|
|
PermissionSysconsoleReadSitePublicLinks.Id,
|
|
PermissionSysconsoleReadSiteNotices.Id,
|
|
PermissionSysconsoleReadAuthenticationSignup.Id,
|
|
PermissionSysconsoleReadAuthenticationEmail.Id,
|
|
PermissionSysconsoleReadAuthenticationPassword.Id,
|
|
PermissionSysconsoleReadAuthenticationMfa.Id,
|
|
PermissionSysconsoleReadAuthenticationLdap.Id,
|
|
PermissionSysconsoleReadAuthenticationSaml.Id,
|
|
PermissionSysconsoleReadAuthenticationOpenid.Id,
|
|
PermissionSysconsoleReadAuthenticationGuestAccess.Id,
|
|
PermissionSysconsoleReadPlugins.Id,
|
|
PermissionSysconsoleReadIntegrationsIntegrationManagement.Id,
|
|
PermissionSysconsoleReadIntegrationsBotAccounts.Id,
|
|
PermissionSysconsoleReadIntegrationsGif.Id,
|
|
PermissionSysconsoleReadIntegrationsCors.Id,
|
|
PermissionSysconsoleReadComplianceDataRetentionPolicy.Id,
|
|
PermissionSysconsoleReadComplianceComplianceExport.Id,
|
|
PermissionSysconsoleReadComplianceComplianceMonitoring.Id,
|
|
PermissionSysconsoleReadComplianceCustomTermsOfService.Id,
|
|
PermissionSysconsoleReadExperimentalFeatures.Id,
|
|
PermissionSysconsoleReadExperimentalFeatureFlags.Id,
|
|
PermissionSysconsoleReadProductsBoards.Id,
|
|
}
|
|
|
|
SystemManagerDefaultPermissions = []string{
|
|
PermissionSysconsoleReadAboutEditionAndLicense.Id,
|
|
PermissionSysconsoleReadReportingSiteStatistics.Id,
|
|
PermissionSysconsoleReadReportingTeamStatistics.Id,
|
|
PermissionSysconsoleReadReportingServerLogs.Id,
|
|
PermissionSysconsoleReadUserManagementGroups.Id,
|
|
PermissionSysconsoleReadUserManagementTeams.Id,
|
|
PermissionSysconsoleReadUserManagementChannels.Id,
|
|
PermissionSysconsoleReadUserManagementPermissions.Id,
|
|
PermissionSysconsoleWriteUserManagementGroups.Id,
|
|
PermissionSysconsoleWriteUserManagementTeams.Id,
|
|
PermissionSysconsoleWriteUserManagementChannels.Id,
|
|
PermissionSysconsoleWriteUserManagementPermissions.Id,
|
|
PermissionSysconsoleReadEnvironmentWebServer.Id,
|
|
PermissionSysconsoleReadEnvironmentDatabase.Id,
|
|
PermissionSysconsoleReadEnvironmentElasticsearch.Id,
|
|
PermissionSysconsoleReadEnvironmentFileStorage.Id,
|
|
PermissionSysconsoleReadEnvironmentImageProxy.Id,
|
|
PermissionSysconsoleReadEnvironmentSMTP.Id,
|
|
PermissionSysconsoleReadEnvironmentPushNotificationServer.Id,
|
|
PermissionSysconsoleReadEnvironmentHighAvailability.Id,
|
|
PermissionSysconsoleReadEnvironmentRateLimiting.Id,
|
|
PermissionSysconsoleReadEnvironmentLogging.Id,
|
|
PermissionSysconsoleReadEnvironmentSessionLengths.Id,
|
|
PermissionSysconsoleReadEnvironmentPerformanceMonitoring.Id,
|
|
PermissionSysconsoleReadEnvironmentDeveloper.Id,
|
|
PermissionSysconsoleWriteEnvironmentWebServer.Id,
|
|
PermissionSysconsoleWriteEnvironmentDatabase.Id,
|
|
PermissionSysconsoleWriteEnvironmentElasticsearch.Id,
|
|
PermissionSysconsoleWriteEnvironmentFileStorage.Id,
|
|
PermissionSysconsoleWriteEnvironmentImageProxy.Id,
|
|
PermissionSysconsoleWriteEnvironmentSMTP.Id,
|
|
PermissionSysconsoleWriteEnvironmentPushNotificationServer.Id,
|
|
PermissionSysconsoleWriteEnvironmentHighAvailability.Id,
|
|
PermissionSysconsoleWriteEnvironmentRateLimiting.Id,
|
|
PermissionSysconsoleWriteEnvironmentLogging.Id,
|
|
PermissionSysconsoleWriteEnvironmentSessionLengths.Id,
|
|
PermissionSysconsoleWriteEnvironmentPerformanceMonitoring.Id,
|
|
PermissionSysconsoleWriteEnvironmentDeveloper.Id,
|
|
PermissionSysconsoleReadSiteCustomization.Id,
|
|
PermissionSysconsoleWriteSiteCustomization.Id,
|
|
PermissionSysconsoleReadSiteLocalization.Id,
|
|
PermissionSysconsoleWriteSiteLocalization.Id,
|
|
PermissionSysconsoleReadSiteUsersAndTeams.Id,
|
|
PermissionSysconsoleWriteSiteUsersAndTeams.Id,
|
|
PermissionSysconsoleReadSiteNotifications.Id,
|
|
PermissionSysconsoleWriteSiteNotifications.Id,
|
|
PermissionSysconsoleReadSiteAnnouncementBanner.Id,
|
|
PermissionSysconsoleWriteSiteAnnouncementBanner.Id,
|
|
PermissionSysconsoleReadSiteEmoji.Id,
|
|
PermissionSysconsoleWriteSiteEmoji.Id,
|
|
PermissionSysconsoleReadSitePosts.Id,
|
|
PermissionSysconsoleWriteSitePosts.Id,
|
|
PermissionSysconsoleReadSiteFileSharingAndDownloads.Id,
|
|
PermissionSysconsoleWriteSiteFileSharingAndDownloads.Id,
|
|
PermissionSysconsoleReadSitePublicLinks.Id,
|
|
PermissionSysconsoleWriteSitePublicLinks.Id,
|
|
PermissionSysconsoleReadSiteNotices.Id,
|
|
PermissionSysconsoleWriteSiteNotices.Id,
|
|
PermissionSysconsoleReadAuthenticationSignup.Id,
|
|
PermissionSysconsoleReadAuthenticationEmail.Id,
|
|
PermissionSysconsoleReadAuthenticationPassword.Id,
|
|
PermissionSysconsoleReadAuthenticationMfa.Id,
|
|
PermissionSysconsoleReadAuthenticationLdap.Id,
|
|
PermissionSysconsoleReadAuthenticationSaml.Id,
|
|
PermissionSysconsoleReadAuthenticationOpenid.Id,
|
|
PermissionSysconsoleReadAuthenticationGuestAccess.Id,
|
|
PermissionSysconsoleReadPlugins.Id,
|
|
PermissionSysconsoleReadIntegrationsIntegrationManagement.Id,
|
|
PermissionSysconsoleReadIntegrationsBotAccounts.Id,
|
|
PermissionSysconsoleReadIntegrationsGif.Id,
|
|
PermissionSysconsoleReadIntegrationsCors.Id,
|
|
PermissionSysconsoleWriteIntegrationsIntegrationManagement.Id,
|
|
PermissionSysconsoleWriteIntegrationsBotAccounts.Id,
|
|
PermissionSysconsoleWriteIntegrationsGif.Id,
|
|
PermissionSysconsoleWriteIntegrationsCors.Id,
|
|
PermissionSysconsoleReadProductsBoards.Id,
|
|
PermissionSysconsoleWriteProductsBoards.Id,
|
|
PermissionManageOutgoingOAuthConnections.Id,
|
|
}
|
|
|
|
SystemCustomGroupAdminDefaultPermissions = []string{
|
|
PermissionCreateCustomGroup.Id,
|
|
PermissionEditCustomGroup.Id,
|
|
PermissionDeleteCustomGroup.Id,
|
|
PermissionRestoreCustomGroup.Id,
|
|
PermissionManageCustomGroupMembers.Id,
|
|
}
|
|
|
|
SharedChannelManagerDefaultPermissions = []string{
|
|
PermissionManageSharedChannels.Id,
|
|
}
|
|
|
|
// Add the ancillary permissions to each system role
|
|
SystemUserManagerDefaultPermissions = AddAncillaryPermissions(SystemUserManagerDefaultPermissions)
|
|
SystemReadOnlyAdminDefaultPermissions = AddAncillaryPermissions(SystemReadOnlyAdminDefaultPermissions)
|
|
SystemManagerDefaultPermissions = AddAncillaryPermissions(SystemManagerDefaultPermissions)
|
|
SystemCustomGroupAdminDefaultPermissions = AddAncillaryPermissions(SystemCustomGroupAdminDefaultPermissions)
|
|
}
|
|
|
|
type RoleType string
|
|
type RoleScope string
|
|
|
|
const (
|
|
SystemGuestRoleId = "system_guest"
|
|
SystemUserRoleId = "system_user"
|
|
SystemAdminRoleId = "system_admin"
|
|
SystemPostAllRoleId = "system_post_all"
|
|
SystemPostAllPublicRoleId = "system_post_all_public"
|
|
SystemUserAccessTokenRoleId = "system_user_access_token"
|
|
SystemUserManagerRoleId = "system_user_manager"
|
|
SystemReadOnlyAdminRoleId = "system_read_only_admin"
|
|
SystemManagerRoleId = "system_manager"
|
|
SystemCustomGroupAdminRoleId = "system_custom_group_admin"
|
|
SharedChannelManagerRoleId = "system_shared_channel_manager"
|
|
|
|
TeamGuestRoleId = "team_guest"
|
|
TeamUserRoleId = "team_user"
|
|
TeamAdminRoleId = "team_admin"
|
|
TeamPostAllRoleId = "team_post_all"
|
|
TeamPostAllPublicRoleId = "team_post_all_public"
|
|
|
|
ChannelGuestRoleId = "channel_guest"
|
|
ChannelUserRoleId = "channel_user"
|
|
ChannelAdminRoleId = "channel_admin"
|
|
|
|
CustomGroupUserRoleId = "custom_group_user"
|
|
|
|
PlaybookAdminRoleId = "playbook_admin"
|
|
PlaybookMemberRoleId = "playbook_member"
|
|
RunAdminRoleId = "run_admin"
|
|
RunMemberRoleId = "run_member"
|
|
|
|
RoleNameMaxLength = 64
|
|
RoleDisplayNameMaxLength = 128
|
|
RoleDescriptionMaxLength = 1024
|
|
|
|
RoleScopeSystem RoleScope = "System"
|
|
RoleScopeTeam RoleScope = "Team"
|
|
RoleScopeChannel RoleScope = "Channel"
|
|
RoleScopeGroup RoleScope = "Group"
|
|
|
|
RoleTypeGuest RoleType = "Guest"
|
|
RoleTypeUser RoleType = "User"
|
|
RoleTypeAdmin RoleType = "Admin"
|
|
)
|
|
|
|
type Role 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"`
|
|
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 {
|
|
return map[string]any{
|
|
"id": r.Id,
|
|
"name": r.Name,
|
|
"display_name": r.DisplayName,
|
|
"description": r.Description,
|
|
"create_at": r.CreateAt,
|
|
"update_at": r.UpdateAt,
|
|
"delete_at": r.DeleteAt,
|
|
"permissions": r.Permissions,
|
|
"scheme_managed": r.SchemeManaged,
|
|
"built_in": r.BuiltIn,
|
|
"scheme_id": r.SchemeId,
|
|
}
|
|
}
|
|
|
|
func (r *Role) Sanitize() {
|
|
r.DisplayName = FakeSetting
|
|
r.Description = FakeSetting
|
|
}
|
|
|
|
func (r *Role) MarshalYAML() (any, error) {
|
|
return struct {
|
|
Id string `yaml:"id"`
|
|
Name string `yaml:"name"`
|
|
DisplayName string `yaml:"display_name"`
|
|
Description string `yaml:"description"`
|
|
CreateAt string `yaml:"create_at"`
|
|
UpdateAt string `yaml:"update_at"`
|
|
DeleteAt string `yaml:"delete_at"`
|
|
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,
|
|
DisplayName: r.DisplayName,
|
|
Description: r.Description,
|
|
CreateAt: timeutils.FormatMillis(r.CreateAt),
|
|
UpdateAt: timeutils.FormatMillis(r.UpdateAt),
|
|
DeleteAt: timeutils.FormatMillis(r.DeleteAt),
|
|
Permissions: r.Permissions,
|
|
SchemeManaged: r.SchemeManaged,
|
|
BuiltIn: r.BuiltIn,
|
|
SchemeId: r.SchemeId,
|
|
}, nil
|
|
}
|
|
|
|
func (r *Role) UnmarshalYAML(unmarshal func(any) error) error {
|
|
out := struct {
|
|
Id string `yaml:"id"`
|
|
Name string `yaml:"name"`
|
|
DisplayName string `yaml:"display_name"`
|
|
Description string `yaml:"description"`
|
|
CreateAt string `yaml:"create_at"`
|
|
UpdateAt string `yaml:"update_at"`
|
|
DeleteAt string `yaml:"delete_at"`
|
|
Permissions []string `yaml:"permissions"`
|
|
SchemeManaged bool `yaml:"scheme_managed"`
|
|
BuiltIn bool `yaml:"built_in"`
|
|
SchemeId *string `yaml:"scheme_id"`
|
|
}{}
|
|
|
|
err := unmarshal(&out)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
createAt, err := timeutils.ParseFormatedMillis(out.CreateAt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
updateAt, err := timeutils.ParseFormatedMillis(out.UpdateAt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
deleteAt, err := timeutils.ParseFormatedMillis(out.DeleteAt)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*r = Role{
|
|
Id: out.Id,
|
|
Name: out.Name,
|
|
DisplayName: out.DisplayName,
|
|
Description: out.Description,
|
|
CreateAt: createAt,
|
|
UpdateAt: updateAt,
|
|
DeleteAt: deleteAt,
|
|
Permissions: out.Permissions,
|
|
SchemeManaged: out.SchemeManaged,
|
|
BuiltIn: out.BuiltIn,
|
|
SchemeId: out.SchemeId,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type RolePatch struct {
|
|
Permissions *[]string `json:"permissions"`
|
|
}
|
|
|
|
func (r *RolePatch) Auditable() map[string]any {
|
|
return map[string]any{
|
|
"permissions": r.Permissions,
|
|
}
|
|
}
|
|
|
|
type RolePermissions struct {
|
|
RoleID string
|
|
Permissions []string
|
|
}
|
|
|
|
func (r *Role) Patch(patch *RolePatch) {
|
|
if patch.Permissions != nil {
|
|
r.Permissions = *patch.Permissions
|
|
}
|
|
}
|
|
|
|
func (r *Role) CreateAt_() float64 {
|
|
return float64(r.CreateAt)
|
|
}
|
|
|
|
func (r *Role) UpdateAt_() float64 {
|
|
return float64(r.UpdateAt)
|
|
}
|
|
|
|
func (r *Role) DeleteAt_() float64 {
|
|
return float64(r.DeleteAt)
|
|
}
|
|
|
|
// MergeChannelHigherScopedPermissions is meant to be invoked on a channel scheme's role and merges the higher-scoped
|
|
// channel role's permissions.
|
|
func (r *Role) MergeChannelHigherScopedPermissions(higherScopedPermissions *RolePermissions) {
|
|
mergedPermissions := []string{}
|
|
|
|
higherScopedPermissionsMap := asStringBoolMap(higherScopedPermissions.Permissions)
|
|
rolePermissionsMap := asStringBoolMap(r.Permissions)
|
|
|
|
for _, cp := range AllPermissions {
|
|
if cp.Scope != PermissionScopeChannel {
|
|
continue
|
|
}
|
|
|
|
_, presentOnHigherScope := higherScopedPermissionsMap[cp.Id]
|
|
|
|
// For the channel admin role always look to the higher scope to determine if the role has their permission.
|
|
// The channel admin is a special case because they're not part of the UI to be "channel moderated", only
|
|
// channel members and channel guests are.
|
|
if higherScopedPermissions.RoleID == ChannelAdminRoleId && presentOnHigherScope {
|
|
mergedPermissions = append(mergedPermissions, cp.Id)
|
|
continue
|
|
}
|
|
|
|
_, permissionIsModerated := ChannelModeratedPermissionsMap[cp.Id]
|
|
if permissionIsModerated {
|
|
_, presentOnRole := rolePermissionsMap[cp.Id]
|
|
if presentOnRole && presentOnHigherScope {
|
|
mergedPermissions = append(mergedPermissions, cp.Id)
|
|
}
|
|
} else {
|
|
if presentOnHigherScope {
|
|
mergedPermissions = append(mergedPermissions, cp.Id)
|
|
}
|
|
}
|
|
}
|
|
|
|
r.Permissions = mergedPermissions
|
|
}
|
|
|
|
// Returns an array of permissions that are in either role.Permissions
|
|
// or patch.Permissions, but not both.
|
|
func PermissionsChangedByPatch(role *Role, patch *RolePatch) []string {
|
|
var result []string
|
|
|
|
if patch.Permissions == nil {
|
|
return result
|
|
}
|
|
|
|
roleMap := make(map[string]bool)
|
|
patchMap := make(map[string]bool)
|
|
|
|
for _, permission := range role.Permissions {
|
|
roleMap[permission] = true
|
|
}
|
|
|
|
for _, permission := range *patch.Permissions {
|
|
patchMap[permission] = true
|
|
}
|
|
|
|
for _, permission := range role.Permissions {
|
|
if !patchMap[permission] {
|
|
result = append(result, permission)
|
|
}
|
|
}
|
|
|
|
for _, permission := range *patch.Permissions {
|
|
if !roleMap[permission] {
|
|
result = append(result, permission)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func ChannelModeratedPermissionsChangedByPatch(role *Role, patch *RolePatch) []string {
|
|
var result []string
|
|
|
|
if role == nil {
|
|
return result
|
|
}
|
|
|
|
if patch.Permissions == nil {
|
|
return result
|
|
}
|
|
|
|
roleMap := make(map[string]bool)
|
|
patchMap := make(map[string]bool)
|
|
|
|
for _, permission := range role.Permissions {
|
|
if channelModeratedPermissionName, found := ChannelModeratedPermissionsMap[permission]; found {
|
|
roleMap[channelModeratedPermissionName] = true
|
|
}
|
|
}
|
|
|
|
for _, permission := range *patch.Permissions {
|
|
if channelModeratedPermissionName, found := ChannelModeratedPermissionsMap[permission]; found {
|
|
patchMap[channelModeratedPermissionName] = true
|
|
}
|
|
}
|
|
|
|
for permissionKey := range roleMap {
|
|
if !patchMap[permissionKey] {
|
|
result = append(result, permissionKey)
|
|
}
|
|
}
|
|
|
|
for permissionKey := range patchMap {
|
|
if !roleMap[permissionKey] {
|
|
result = append(result, permissionKey)
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func isModeratedBookmarkPermission(permission string) bool {
|
|
for _, mbp := range ModeratedBookmarkPermissions {
|
|
if mbp.Id == permission {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetChannelModeratedPermissions returns a map of channel moderated permissions that the role has access to
|
|
func (r *Role) GetChannelModeratedPermissions(channelType ChannelType) map[string]bool {
|
|
moderatedPermissions := make(map[string]bool)
|
|
for _, permission := range r.Permissions {
|
|
if _, found := ChannelModeratedPermissionsMap[permission]; !found {
|
|
continue
|
|
}
|
|
|
|
for moderated, moderatedPermissionValue := range ChannelModeratedPermissionsMap {
|
|
// the moderated permission has already been found to be true so skip this iteration
|
|
if moderatedPermissions[moderatedPermissionValue] {
|
|
continue
|
|
}
|
|
|
|
if moderated == permission {
|
|
// Special case where the channel moderated permission for `manage_members` is different depending
|
|
// on whether the channel is private or public
|
|
if moderated == PermissionManagePublicChannelMembers.Id || moderated == PermissionManagePrivateChannelMembers.Id {
|
|
canManagePublic := channelType == ChannelTypeOpen && moderated == PermissionManagePublicChannelMembers.Id
|
|
canManagePrivate := channelType == ChannelTypePrivate && moderated == PermissionManagePrivateChannelMembers.Id
|
|
moderatedPermissions[moderatedPermissionValue] = canManagePublic || canManagePrivate
|
|
|
|
// Special case where the channel moderated permission for `manage_bookmarks` is different
|
|
// depending on whether the channel is private or public.
|
|
//
|
|
// Only AddBookmark is checked even if the permission includes four (add, delete, edit and
|
|
// order) as all of them are enabled or disabled in together
|
|
} else if isModeratedBookmarkPermission(moderated) {
|
|
canManagePublic := channelType == ChannelTypeOpen && moderated == PermissionAddBookmarkPublicChannel.Id
|
|
canManagePrivate := channelType == ChannelTypePrivate && moderated == PermissionAddBookmarkPrivateChannel.Id
|
|
moderatedPermissions[moderatedPermissionValue] = canManagePublic || canManagePrivate
|
|
} else {
|
|
moderatedPermissions[moderatedPermissionValue] = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return moderatedPermissions
|
|
}
|
|
|
|
// RolePatchFromChannelModerationsPatch Creates and returns a RolePatch based on a slice of ChannelModerationPatches, roleName is expected to be either "members" or "guests".
|
|
func (r *Role) RolePatchFromChannelModerationsPatch(channelModerationsPatch []*ChannelModerationPatch, roleName string) *RolePatch {
|
|
permissionsToAddToPatch := make(map[string]bool)
|
|
|
|
// Iterate through the list of existing permissions on the role and append permissions that we want to keep.
|
|
for _, permission := range r.Permissions {
|
|
// Permission is not moderated so dont add it to the patch and skip the channelModerationsPatch
|
|
if _, isModerated := ChannelModeratedPermissionsMap[permission]; !isModerated {
|
|
continue
|
|
}
|
|
|
|
permissionEnabled := true
|
|
// Check if permission has a matching moderated permission name inside the channel moderation patch
|
|
for _, channelModerationPatch := range channelModerationsPatch {
|
|
if *channelModerationPatch.Name == ChannelModeratedPermissionsMap[permission] {
|
|
// Permission key exists in patch with a value of false so skip over it
|
|
if roleName == "members" {
|
|
if channelModerationPatch.Roles.Members != nil && !*channelModerationPatch.Roles.Members {
|
|
permissionEnabled = false
|
|
}
|
|
} else if roleName == "guests" {
|
|
if channelModerationPatch.Roles.Guests != nil && !*channelModerationPatch.Roles.Guests {
|
|
permissionEnabled = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if permissionEnabled {
|
|
permissionsToAddToPatch[permission] = true
|
|
}
|
|
}
|
|
|
|
// Iterate through the patch and add any permissions that dont already exist on the role
|
|
for _, channelModerationPatch := range channelModerationsPatch {
|
|
for permission, moderatedPermissionName := range ChannelModeratedPermissionsMap {
|
|
if roleName == "members" && channelModerationPatch.Roles.Members != nil && *channelModerationPatch.Roles.Members && *channelModerationPatch.Name == moderatedPermissionName {
|
|
permissionsToAddToPatch[permission] = true
|
|
}
|
|
|
|
if roleName == "guests" && channelModerationPatch.Roles.Guests != nil && *channelModerationPatch.Roles.Guests && *channelModerationPatch.Name == moderatedPermissionName {
|
|
permissionsToAddToPatch[permission] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
patchPermissions := make([]string, 0, len(permissionsToAddToPatch))
|
|
for permission := range permissionsToAddToPatch {
|
|
patchPermissions = append(patchPermissions, permission)
|
|
}
|
|
|
|
return &RolePatch{Permissions: &patchPermissions}
|
|
}
|
|
|
|
func (r *Role) IsValid() error {
|
|
if !IsValidId(r.Id) {
|
|
return fmt.Errorf("invalid role id %q", r.Id)
|
|
}
|
|
|
|
return r.IsValidWithoutId()
|
|
}
|
|
|
|
func (r *Role) IsValidWithoutId() error {
|
|
if !IsValidRoleName(r.Name) {
|
|
return fmt.Errorf("invalid role name %q", r.Name)
|
|
}
|
|
|
|
if r.DisplayName == "" {
|
|
return fmt.Errorf("role display name must not be empty")
|
|
}
|
|
if len(r.DisplayName) > RoleDisplayNameMaxLength {
|
|
return fmt.Errorf("role display name %q exceeds maximum length of %d", r.DisplayName, RoleDisplayNameMaxLength)
|
|
}
|
|
|
|
if len(r.Description) > RoleDescriptionMaxLength {
|
|
return fmt.Errorf("role description exceeds maximum length of %d", RoleDescriptionMaxLength)
|
|
}
|
|
|
|
check := func(perms []*Permission, permission string) bool {
|
|
for _, p := range perms {
|
|
if permission == p.Id {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
for _, permission := range r.Permissions {
|
|
if !check(AllPermissions, permission) && !check(DeprecatedPermissions, permission) {
|
|
return fmt.Errorf("unknown permission %q", permission)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func CleanRoleNames(roleNames []string) ([]string, bool) {
|
|
var cleanedRoleNames []string
|
|
for _, roleName := range roleNames {
|
|
if strings.TrimSpace(roleName) == "" {
|
|
continue
|
|
}
|
|
|
|
if !IsValidRoleName(roleName) {
|
|
return roleNames, false
|
|
}
|
|
|
|
cleanedRoleNames = append(cleanedRoleNames, roleName)
|
|
}
|
|
|
|
return cleanedRoleNames, true
|
|
}
|
|
|
|
func IsValidRoleName(roleName string) bool {
|
|
if roleName == "" || len(roleName) > RoleNameMaxLength {
|
|
return false
|
|
}
|
|
|
|
if strings.TrimLeft(roleName, "abcdefghijklmnopqrstuvwxyz0123456789_") != "" {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func MakeDefaultRoles() map[string]*Role {
|
|
roles := make(map[string]*Role)
|
|
|
|
roles[CustomGroupUserRoleId] = &Role{
|
|
Name: CustomGroupUserRoleId,
|
|
DisplayName: fmt.Sprintf("authentication.roles.%s.name", CustomGroupUserRoleId),
|
|
Description: fmt.Sprintf("authentication.roles.%s.description", CustomGroupUserRoleId),
|
|
Permissions: []string{},
|
|
}
|
|
|
|
roles[ChannelGuestRoleId] = &Role{
|
|
Name: "channel_guest",
|
|
DisplayName: "authentication.roles.channel_guest.name",
|
|
Description: "authentication.roles.channel_guest.description",
|
|
Permissions: []string{
|
|
PermissionReadChannel.Id,
|
|
PermissionReadChannelContent.Id,
|
|
PermissionAddReaction.Id,
|
|
PermissionRemoveReaction.Id,
|
|
PermissionUploadFile.Id,
|
|
PermissionEditPost.Id,
|
|
PermissionCreatePost.Id,
|
|
PermissionUseChannelMentions.Id,
|
|
PermissionEditFileAttachment.Id,
|
|
},
|
|
SchemeManaged: true,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[ChannelUserRoleId] = &Role{
|
|
Name: "channel_user",
|
|
DisplayName: "authentication.roles.channel_user.name",
|
|
Description: "authentication.roles.channel_user.description",
|
|
Permissions: []string{
|
|
PermissionReadChannel.Id,
|
|
PermissionReadChannelContent.Id,
|
|
PermissionAddReaction.Id,
|
|
PermissionRemoveReaction.Id,
|
|
PermissionManagePublicChannelMembers.Id,
|
|
PermissionUploadFile.Id,
|
|
PermissionGetPublicLink.Id,
|
|
PermissionCreatePost.Id,
|
|
PermissionUseChannelMentions.Id,
|
|
PermissionManagePublicChannelProperties.Id,
|
|
PermissionDeletePublicChannel.Id,
|
|
PermissionManagePrivateChannelProperties.Id,
|
|
PermissionDeletePrivateChannel.Id,
|
|
PermissionManagePrivateChannelMembers.Id,
|
|
PermissionDeletePost.Id,
|
|
PermissionEditPost.Id,
|
|
PermissionEditFileAttachment.Id,
|
|
PermissionAddBookmarkPublicChannel.Id,
|
|
PermissionEditBookmarkPublicChannel.Id,
|
|
PermissionDeleteBookmarkPublicChannel.Id,
|
|
PermissionOrderBookmarkPublicChannel.Id,
|
|
PermissionAddBookmarkPrivateChannel.Id,
|
|
PermissionEditBookmarkPrivateChannel.Id,
|
|
PermissionDeleteBookmarkPrivateChannel.Id,
|
|
PermissionOrderBookmarkPrivateChannel.Id,
|
|
},
|
|
SchemeManaged: true,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[ChannelAdminRoleId] = &Role{
|
|
Name: "channel_admin",
|
|
DisplayName: "authentication.roles.channel_admin.name",
|
|
Description: "authentication.roles.channel_admin.description",
|
|
Permissions: []string{
|
|
PermissionManageChannelRoles.Id,
|
|
PermissionUseGroupMentions.Id,
|
|
PermissionAddBookmarkPublicChannel.Id,
|
|
PermissionEditBookmarkPublicChannel.Id,
|
|
PermissionDeleteBookmarkPublicChannel.Id,
|
|
PermissionOrderBookmarkPublicChannel.Id,
|
|
PermissionAddBookmarkPrivateChannel.Id,
|
|
PermissionEditBookmarkPrivateChannel.Id,
|
|
PermissionDeleteBookmarkPrivateChannel.Id,
|
|
PermissionOrderBookmarkPrivateChannel.Id,
|
|
PermissionManagePublicChannelBanner.Id,
|
|
PermissionManagePrivateChannelBanner.Id,
|
|
PermissionManageChannelAccessRules.Id,
|
|
PermissionManagePublicChannelAutoTranslation.Id,
|
|
PermissionManagePrivateChannelAutoTranslation.Id,
|
|
PermissionManagePrivateChannelDiscoverability.Id,
|
|
PermissionManageChannelJoinRequests.Id,
|
|
},
|
|
SchemeManaged: true,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[TeamGuestRoleId] = &Role{
|
|
Name: "team_guest",
|
|
DisplayName: "authentication.roles.team_guest.name",
|
|
Description: "authentication.roles.team_guest.description",
|
|
Permissions: []string{
|
|
PermissionViewTeam.Id,
|
|
},
|
|
SchemeManaged: true,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[TeamUserRoleId] = &Role{
|
|
Name: "team_user",
|
|
DisplayName: "authentication.roles.team_user.name",
|
|
Description: "authentication.roles.team_user.description",
|
|
Permissions: []string{
|
|
PermissionListTeamChannels.Id,
|
|
PermissionJoinPublicChannels.Id,
|
|
PermissionReadPublicChannel.Id,
|
|
PermissionViewTeam.Id,
|
|
PermissionCreatePublicChannel.Id,
|
|
PermissionCreatePrivateChannel.Id,
|
|
PermissionInviteUser.Id,
|
|
PermissionAddUserToTeam.Id,
|
|
},
|
|
SchemeManaged: true,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[TeamPostAllRoleId] = &Role{
|
|
Name: "team_post_all",
|
|
DisplayName: "authentication.roles.team_post_all.name",
|
|
Description: "authentication.roles.team_post_all.description",
|
|
Permissions: []string{
|
|
PermissionCreatePost.Id,
|
|
PermissionUseChannelMentions.Id,
|
|
},
|
|
SchemeManaged: false,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[TeamPostAllPublicRoleId] = &Role{
|
|
Name: "team_post_all_public",
|
|
DisplayName: "authentication.roles.team_post_all_public.name",
|
|
Description: "authentication.roles.team_post_all_public.description",
|
|
Permissions: []string{
|
|
PermissionCreatePostPublic.Id,
|
|
PermissionUseChannelMentions.Id,
|
|
},
|
|
SchemeManaged: false,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[TeamAdminRoleId] = &Role{
|
|
Name: "team_admin",
|
|
DisplayName: "authentication.roles.team_admin.name",
|
|
Description: "authentication.roles.team_admin.description",
|
|
Permissions: []string{
|
|
PermissionRemoveUserFromTeam.Id,
|
|
PermissionManageTeam.Id,
|
|
PermissionImportTeam.Id,
|
|
PermissionManageTeamRoles.Id,
|
|
PermissionManageTeamAccessRules.Id,
|
|
PermissionManageChannelRoles.Id,
|
|
PermissionManageOwnIncomingWebhooks.Id,
|
|
PermissionManageOthersIncomingWebhooks.Id,
|
|
PermissionManageOwnOutgoingWebhooks.Id,
|
|
PermissionManageOthersOutgoingWebhooks.Id,
|
|
PermissionManageOwnSlashCommands.Id,
|
|
PermissionManageOthersSlashCommands.Id,
|
|
PermissionBypassIncomingWebhookChannelLock.Id,
|
|
PermissionConvertPublicChannelToPrivate.Id,
|
|
PermissionConvertPrivateChannelToPublic.Id,
|
|
PermissionDeletePost.Id,
|
|
PermissionDeleteOthersPosts.Id,
|
|
PermissionAddBookmarkPublicChannel.Id,
|
|
PermissionEditBookmarkPublicChannel.Id,
|
|
PermissionDeleteBookmarkPublicChannel.Id,
|
|
PermissionOrderBookmarkPublicChannel.Id,
|
|
PermissionAddBookmarkPrivateChannel.Id,
|
|
PermissionEditBookmarkPrivateChannel.Id,
|
|
PermissionDeleteBookmarkPrivateChannel.Id,
|
|
PermissionOrderBookmarkPrivateChannel.Id,
|
|
PermissionManagePublicChannelBanner.Id,
|
|
PermissionManagePrivateChannelBanner.Id,
|
|
PermissionManageChannelAccessRules.Id,
|
|
},
|
|
SchemeManaged: true,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[PlaybookAdminRoleId] = &Role{
|
|
Name: PlaybookAdminRoleId,
|
|
DisplayName: "authentication.roles.playbook_admin.name",
|
|
Description: "authentication.roles.playbook_admin.description",
|
|
Permissions: []string{
|
|
PermissionPublicPlaybookManageMembers.Id,
|
|
PermissionPublicPlaybookManageRoles.Id,
|
|
PermissionPublicPlaybookManageProperties.Id,
|
|
PermissionPrivatePlaybookManageMembers.Id,
|
|
PermissionPrivatePlaybookManageRoles.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",
|
|
Description: "authentication.roles.global_guest.description",
|
|
Permissions: []string{
|
|
PermissionCreateDirectChannel.Id,
|
|
PermissionCreateGroupChannel.Id,
|
|
},
|
|
SchemeManaged: true,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[SystemUserRoleId] = &Role{
|
|
Name: "system_user",
|
|
DisplayName: "authentication.roles.global_user.name",
|
|
Description: "authentication.roles.global_user.description",
|
|
Permissions: []string{
|
|
PermissionListPublicTeams.Id,
|
|
PermissionJoinPublicTeams.Id,
|
|
PermissionCreateDirectChannel.Id,
|
|
PermissionCreateGroupChannel.Id,
|
|
PermissionViewMembers.Id,
|
|
PermissionCreateTeam.Id,
|
|
PermissionCreateCustomGroup.Id,
|
|
PermissionEditCustomGroup.Id,
|
|
PermissionDeleteCustomGroup.Id,
|
|
PermissionRestoreCustomGroup.Id,
|
|
PermissionManageCustomGroupMembers.Id,
|
|
PermissionManageOwnAgent.Id,
|
|
},
|
|
SchemeManaged: true,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[SystemPostAllRoleId] = &Role{
|
|
Name: "system_post_all",
|
|
DisplayName: "authentication.roles.system_post_all.name",
|
|
Description: "authentication.roles.system_post_all.description",
|
|
Permissions: []string{
|
|
PermissionCreatePost.Id,
|
|
PermissionUseChannelMentions.Id,
|
|
},
|
|
SchemeManaged: false,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[SystemPostAllPublicRoleId] = &Role{
|
|
Name: "system_post_all_public",
|
|
DisplayName: "authentication.roles.system_post_all_public.name",
|
|
Description: "authentication.roles.system_post_all_public.description",
|
|
Permissions: []string{
|
|
PermissionCreatePostPublic.Id,
|
|
PermissionUseChannelMentions.Id,
|
|
},
|
|
SchemeManaged: false,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[SystemUserAccessTokenRoleId] = &Role{
|
|
Name: "system_user_access_token",
|
|
DisplayName: "authentication.roles.system_user_access_token.name",
|
|
Description: "authentication.roles.system_user_access_token.description",
|
|
Permissions: []string{
|
|
PermissionCreateUserAccessToken.Id,
|
|
PermissionReadUserAccessToken.Id,
|
|
PermissionRevokeUserAccessToken.Id,
|
|
},
|
|
SchemeManaged: false,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[SystemUserManagerRoleId] = &Role{
|
|
Name: "system_user_manager",
|
|
DisplayName: "authentication.roles.system_user_manager.name",
|
|
Description: "authentication.roles.system_user_manager.description",
|
|
Permissions: SystemUserManagerDefaultPermissions,
|
|
SchemeManaged: false,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[SystemReadOnlyAdminRoleId] = &Role{
|
|
Name: "system_read_only_admin",
|
|
DisplayName: "authentication.roles.system_read_only_admin.name",
|
|
Description: "authentication.roles.system_read_only_admin.description",
|
|
Permissions: SystemReadOnlyAdminDefaultPermissions,
|
|
SchemeManaged: false,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[SystemManagerRoleId] = &Role{
|
|
Name: "system_manager",
|
|
DisplayName: "authentication.roles.system_manager.name",
|
|
Description: "authentication.roles.system_manager.description",
|
|
Permissions: SystemManagerDefaultPermissions,
|
|
SchemeManaged: false,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[SystemCustomGroupAdminRoleId] = &Role{
|
|
Name: "system_custom_group_admin",
|
|
DisplayName: "authentication.roles.system_custom_group_admin.name",
|
|
Description: "authentication.roles.system_custom_group_admin.description",
|
|
Permissions: SystemCustomGroupAdminDefaultPermissions,
|
|
SchemeManaged: false,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
roles[SharedChannelManagerRoleId] = &Role{
|
|
Name: SharedChannelManagerRoleId,
|
|
DisplayName: "authentication.roles.system_shared_channel_manager.name",
|
|
Description: "authentication.roles.system_shared_channel_manager.description",
|
|
Permissions: SharedChannelManagerDefaultPermissions,
|
|
SchemeManaged: false,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
allPermissionIDs := []string{}
|
|
for _, permission := range AllPermissions {
|
|
allPermissionIDs = append(allPermissionIDs, permission.Id)
|
|
}
|
|
|
|
roles[SystemAdminRoleId] = &Role{
|
|
Name: "system_admin",
|
|
DisplayName: "authentication.roles.global_admin.name",
|
|
Description: "authentication.roles.global_admin.description",
|
|
// System admins can do anything channel and team admins can do
|
|
// plus everything members of teams and channels can do to all teams
|
|
// and channels on the system
|
|
Permissions: allPermissionIDs,
|
|
SchemeManaged: true,
|
|
BuiltIn: true,
|
|
}
|
|
|
|
return roles
|
|
}
|
|
|
|
func AddAncillaryPermissions(permissions []string) []string {
|
|
for _, permission := range permissions {
|
|
if ancillaryPermissions, ok := SysconsoleAncillaryPermissions[permission]; ok {
|
|
for _, ancillaryPermission := range ancillaryPermissions {
|
|
permissions = append(permissions, ancillaryPermission.Id)
|
|
}
|
|
}
|
|
}
|
|
return permissions
|
|
}
|
|
|
|
func asStringBoolMap(list []string) map[string]bool {
|
|
listMap := make(map[string]bool, len(list))
|
|
for _, p := range list {
|
|
listMap[p] = true
|
|
}
|
|
return listMap
|
|
}
|