diff --git a/server/enterprise/license.go b/server/enterprise/license.go index ddc247529..33c6709de 100644 --- a/server/enterprise/license.go +++ b/server/enterprise/license.go @@ -58,5 +58,15 @@ func (e *LicenseChecker) HostControlsAllowed() bool { } func (e *LicenseChecker) GroupCallsAllowed() bool { - return e.isAtLeastProfessionalLicensed() || os.Getenv("MM_CALLS_GROUP_CALLS_ALLOWED") == "true" + if os.Getenv("MM_CALLS_GROUP_CALLS_ALLOWED") == "true" { + return true + } + if os.Getenv("MM_CALLS_GROUP_CALLS_ALLOWED") == "false" { + return false + } + // Self-hosted deployments support calls in public/private channels without Professional. + if !license.IsCloud(e.api.GetLicense()) { + return true + } + return e.isAtLeastProfessionalLicensed() } diff --git a/server/session_test.go b/server/session_test.go index d8ba1df65..d40715690 100644 --- a/server/session_test.go +++ b/server/session_test.go @@ -106,13 +106,20 @@ func TestAddUserSession(t *testing.T) { require.Equal(t, retState, retState2) }) - t.Run("allow calls in DMs only when unlicensed", func(t *testing.T) { + t.Run("allow calls in DMs only on unlicensed cloud", func(t *testing.T) { defer mockAPI.AssertExpectations(t) defer mockMetrics.AssertExpectations(t) defer ResetTestStore(t, p.store) + cloudLicense := &model.License{ + SkuShortName: "starter", + Features: &model.Features{ + Cloud: model.NewBool(true), + }, + } + mockAPI.On("GetConfig").Return(&model.Config{}, nil).Times(6) - mockAPI.On("GetLicense").Return(&model.License{}, nil).Times(3) + mockAPI.On("GetLicense").Return(cloudLicense, nil).Times(3) t.Run("public channel", func(t *testing.T) { mockAPI.On("SendEphemeralPost", "userA", &model.Post{ @@ -160,4 +167,20 @@ func TestAddUserSession(t *testing.T) { require.NotNil(t, retState.sessions["connA"]) }) }) + + t.Run("allow calls in all channels on self-hosted", func(t *testing.T) { + defer mockAPI.AssertExpectations(t) + defer mockMetrics.AssertExpectations(t) + defer ResetTestStore(t, p.store) + + mockAPI.On("GetConfig").Return(&model.Config{}, nil).Times(2) + mockAPI.On("GetLicense").Return(&model.License{}, nil).Once() + mockMetrics.On("IncWebSocketEvent", "out", wsEventCallHostChanged).Once() + mockAPI.On("PublishWebSocketEvent", wsEventCallHostChanged, mock.Anything, + &model.WebsocketBroadcast{UserId: "userA", ChannelId: "channelID", ReliableClusterSend: true}).Once() + + retState, err := p.addUserSession(nil, model.NewPointer(true), "userA", "connA", "channelID", "", model.ChannelTypeOpen) + require.NoError(t, err) + require.NotNil(t, retState) + }) } diff --git a/webapp/src/components/channel_header_dropdown_button/index.ts b/webapp/src/components/channel_header_dropdown_button/index.ts index a2df73db0..70a173ae5 100644 --- a/webapp/src/components/channel_header_dropdown_button/index.ts +++ b/webapp/src/components/channel_header_dropdown_button/index.ts @@ -6,6 +6,7 @@ import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels'; import {isCurrentUserSystemAdmin} from 'mattermost-redux/selectors/entities/users'; import {connect} from 'react-redux'; import { + areGroupCallsAllowed, callsShowButton, channelIDForCurrentCall, currentChannelHasCall, @@ -14,6 +15,7 @@ import { isLimitRestricted, maxParticipants, } from 'src/selectors'; +import {isDmGmChannel} from 'src/utils'; import ChannelHeaderDropdownButton from './component'; @@ -21,7 +23,7 @@ const mapStateToProps = (state: GlobalState) => { const channel = getCurrentChannel(state); return { - show: callsShowButton(state, channel?.id), + show: callsShowButton(state, channel?.id) && (areGroupCallsAllowed(state) || isDmGmChannel(channel)), inCall: Boolean(channelIDForCurrentCall(state) && channelIDForCurrentCall(state) === channel?.id), hasCall: currentChannelHasCall(state), isAdmin: isCurrentUserSystemAdmin(state),