Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion server/enterprise/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
27 changes: 25 additions & 2 deletions server/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Comment on lines +109 to 113

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Stabilize these tests by pinning MM_CALLS_GROUP_CALLS_ALLOWED.

Both subtests rely on the default branch of GroupCallsAllowed(), but they never set/reset the env var. If the runner environment sets MM_CALLS_GROUP_CALLS_ALLOWED, these expectations can flip and become flaky.

Suggested fix
 t.Run("allow calls in DMs only on unlicensed cloud", func(t *testing.T) {
+    t.Setenv("MM_CALLS_GROUP_CALLS_ALLOWED", "")
     defer mockAPI.AssertExpectations(t)
     defer mockMetrics.AssertExpectations(t)
     defer ResetTestStore(t, p.store)
@@
 t.Run("allow calls in all channels on self-hosted", func(t *testing.T) {
+    t.Setenv("MM_CALLS_GROUP_CALLS_ALLOWED", "")
     defer mockAPI.AssertExpectations(t)
     defer mockMetrics.AssertExpectations(t)
     defer ResetTestStore(t, p.store)

Also applies to: 171-175

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/session_test.go` around lines 109 - 113, To stabilize the flaky tests,
you need to pin the MM_CALLS_GROUP_CALLS_ALLOWED environment variable in both
test subtests to ensure they don't depend on the runner's environment
configuration. For the "allow calls in DMs only on unlicensed cloud" subtest (at
lines 109-113) and the other affected subtest (at lines 171-175), use Go's
testing utilities (such as t.Setenv) to explicitly set the
MM_CALLS_GROUP_CALLS_ALLOWED environment variable to the expected value at the
start of each subtest, ensuring the GroupCallsAllowed() function behavior is
consistent regardless of the host environment's settings.

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{
Expand Down Expand Up @@ -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)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -14,14 +15,15 @@ import {
isLimitRestricted,
maxParticipants,
} from 'src/selectors';
import {isDmGmChannel} from 'src/utils';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

GM channels are being shown as callable where backend still blocks them on unlicensed cloud.

Line 26 uses isDmGmChannel(channel), which includes GM. But backend policy still only exempts direct channels when group calls are disallowed, and the updated backend tests still expect group channels to fail. This creates a UI/backend contract mismatch.

Suggested fix
-import {isDmGmChannel} from 'src/utils';
@@
-        show: callsShowButton(state, channel?.id) && (areGroupCallsAllowed(state) || isDmGmChannel(channel)),
+        show: callsShowButton(state, channel?.id) && (areGroupCallsAllowed(state) || channel?.type === 'D'),

Also applies to: 26-26

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webapp/src/components/channel_header_dropdown_button/index.ts` at line 18,
The code is using isDmGmChannel which includes both direct message and group
message channels, but the backend only permits direct message channels to be
callable when group calls are disallowed on unlicensed cloud. Replace the
isDmGmChannel import and its usage at line 26 with a function or check that only
verifies direct message channels (excluding group message channels) to align the
UI behavior with the backend policy that blocks group channels from being
callable.


import ChannelHeaderDropdownButton from './component';

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),
Expand Down