Skip to content

Commit ec18715

Browse files
committed
Disable air-gapped restriction in FOSS build
1 parent 24d4f7f commit ec18715

7 files changed

Lines changed: 15 additions & 136 deletions

File tree

.github/workflows/devsh-foss-image.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,11 @@ jobs:
212212
- name: Verify FOSS bundle
213213
run: |
214214
set -euo pipefail
215-
docker run --rm --entrypoint sh "${IMAGE}:${IMAGE_TAG}" -lc \
216-
"node --version && ! grep -R 'AirGappedRestriction' -n /app/bundle/programs/server 2>/dev/null"
215+
docker run --rm --entrypoint sh "${IMAGE}:${IMAGE_TAG}" -lc '
216+
node --version
217+
! grep -R "AirGappedRestriction.computeRestriction" -n /app/bundle/programs/server 2>/dev/null
218+
! grep -R "throw new Error.*restricted-workspace" -n /app/bundle/programs/server 2>/dev/null
219+
'
217220
218221
- name: Login to GHCR
219222
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0

DEVSH.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The `DevSH FOSS image` workflow builds a fully self-hosted FOSS Rocket.Chat imag
2121
6. Build and verify the Docker image.
2222
7. Publish immutable tags to `ghcr.io/devsh-graphics-programming/rocketchat-foss`.
2323

24-
The workflow verifies that the runtime bundle does not contain `AirGappedRestriction`, which is the Enterprise air-gapped restriction path that can make isolated official images read-only.
24+
The workflow verifies that the runtime bundle does not call `AirGappedRestriction.computeRestriction` and does not contain the Enterprise `restricted-workspace` patch that can make isolated official images read-only.
2525

2626
## Image Tags
2727

apps/meteor/client/hooks/useAirGappedRestriction.spec.ts

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,11 @@ import { useAirGappedRestriction } from './useAirGappedRestriction';
55

66
// [restricted, warning, remainingDays]
77
describe('useAirGappedRestriction hook', () => {
8-
it('should return [false, false, -1] if setting value is not a number', () => {
9-
const { result } = renderHook(() => useAirGappedRestriction(), {
10-
wrapper: mockAppRoot().withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', -1).build(),
11-
});
12-
13-
expect(result.current).toEqual([false, false, -1]);
14-
});
15-
16-
it('should return [false, false, -1] if user has a license (remaining days is a negative value)', () => {
17-
const { result } = renderHook(() => useAirGappedRestriction(), {
18-
wrapper: mockAppRoot().withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', -1).build(),
19-
});
20-
21-
expect(result.current).toEqual([false, false, -1]);
22-
});
23-
24-
it('should return [false, false, 8] if not on warning or restriction phase', () => {
25-
const { result } = renderHook(() => useAirGappedRestriction(), {
26-
wrapper: mockAppRoot().withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 8).build(),
27-
});
28-
29-
expect(result.current).toEqual([false, false, 8]);
30-
});
31-
32-
it('should return [true, false, 7] if on warning phase', () => {
33-
const { result } = renderHook(() => useAirGappedRestriction(), {
34-
wrapper: mockAppRoot().withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 7).build(),
35-
});
36-
37-
expect(result.current).toEqual([false, true, 7]);
38-
});
39-
40-
it('should return [true, false, 0] if on restriction phase', () => {
8+
it('should ignore the air-gapped restriction setting in the DevSH FOSS build', () => {
419
const { result } = renderHook(() => useAirGappedRestriction(), {
4210
wrapper: mockAppRoot().withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 0).build(),
4311
});
4412

45-
expect(result.current).toEqual([true, false, 0]);
13+
expect(result.current).toEqual([false, false, -1]);
4614
});
4715
});
Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,3 @@
1-
import { useSetting } from '@rocket.chat/ui-contexts';
2-
31
export const useAirGappedRestriction = (): [isRestrictionPhase: boolean, isWarningPhase: boolean, remainingDays: number] => {
4-
const airGappedRestrictionRemainingDays = useSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days');
5-
6-
if (typeof airGappedRestrictionRemainingDays !== 'number') {
7-
return [false, false, -1];
8-
}
9-
10-
// If this value is negative, the user has a license with valid module
11-
if (airGappedRestrictionRemainingDays < 0) {
12-
return [false, false, airGappedRestrictionRemainingDays];
13-
}
14-
15-
const isRestrictionPhase = airGappedRestrictionRemainingDays === 0;
16-
const isWarningPhase = !isRestrictionPhase && airGappedRestrictionRemainingDays <= 7;
17-
18-
return [isRestrictionPhase, isWarningPhase, airGappedRestrictionRemainingDays];
2+
return [false, false, -1];
193
};

apps/meteor/client/sidebar/sections/BannerSection.spec.tsx

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,7 @@ describe('Sidebar -> BannerSection -> Airgapped restriction', () => {
1616
expect(screen.queryByText('air-gapped', { exact: false })).not.toBeInTheDocument();
1717
});
1818

19-
it('Should render null if admin and not restricted or warning', () => {
20-
render(<BannerSection />, {
21-
wrapper: mockAppRoot().withJohnDoe().withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 8).build(),
22-
});
23-
24-
expect(screen.queryByText('air-gapped', { exact: false })).not.toBeInTheDocument();
25-
});
26-
27-
it('Should render warning message if admin and warning phase', () => {
28-
render(<BannerSection />, {
29-
wrapper: mockAppRoot()
30-
.withJohnDoe()
31-
.withRole('admin')
32-
.withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 7)
33-
.build(),
34-
});
35-
36-
expect(screen.getByText('will enter read-only', { exact: false })).toBeInTheDocument();
37-
});
38-
39-
it('Should render restriction message if admin and restricted phase', () => {
40-
render(<BannerSection />, {
41-
wrapper: mockAppRoot()
42-
.withJohnDoe()
43-
.withRole('admin')
44-
.withSetting('Cloud_Workspace_AirGapped_Restrictions_Remaining_Days', 0)
45-
.build(),
46-
});
47-
48-
expect(screen.getByText('is in read-only', { exact: false })).toBeInTheDocument();
49-
});
50-
51-
it('Should render restriction message instead of another banner', () => {
19+
it('Should render null for admins in the DevSH FOSS build', () => {
5220
render(<BannerSection />, {
5321
wrapper: mockAppRoot()
5422
.withJohnDoe()
@@ -58,6 +26,6 @@ describe('Sidebar -> BannerSection -> Airgapped restriction', () => {
5826
.build(),
5927
});
6028

61-
expect(screen.getByText('is in read-only', { exact: false })).toBeInTheDocument();
29+
expect(screen.queryByText('read-only', { exact: false })).not.toBeInTheDocument();
6230
});
6331
});
Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,5 @@
1-
import { AirGappedRestriction } from '@rocket.chat/license';
2-
import { Statistics } from '@rocket.chat/models';
3-
41
import { sendUsageReportAndComputeRestriction } from './usageReport';
52

6-
jest.mock('@rocket.chat/license', () => ({
7-
AirGappedRestriction: {
8-
computeRestriction: jest.fn(),
9-
},
10-
}));
11-
12-
jest.mock('@rocket.chat/models', () => ({
13-
Statistics: {
14-
findLastStatsToken: jest.fn(),
15-
},
16-
}));
17-
183
jest.mock('../../app/statistics/server/functions/sendUsageReport', () => ({
194
sendUsageReport: () => undefined,
205
}));
@@ -24,30 +9,7 @@ describe('sendUsageReportAndComputeRestriction', () => {
249
jest.clearAllMocks();
2510
});
2611

27-
it('should pass statsToken to computeRestriction when provided', async () => {
28-
const mockStatsToken = 'test-token';
29-
await sendUsageReportAndComputeRestriction(mockStatsToken);
30-
31-
expect(AirGappedRestriction.computeRestriction).toHaveBeenCalledWith(mockStatsToken);
32-
expect(Statistics.findLastStatsToken).not.toHaveBeenCalled();
33-
});
34-
35-
it('should use findLastStatsToken result when statsToken is omitted', async () => {
36-
const mockLastToken = 'last-token';
37-
(Statistics.findLastStatsToken as jest.Mock).mockResolvedValue(mockLastToken);
38-
39-
await sendUsageReportAndComputeRestriction();
40-
41-
expect(Statistics.findLastStatsToken).toHaveBeenCalled();
42-
expect(AirGappedRestriction.computeRestriction).toHaveBeenCalledWith(mockLastToken);
43-
});
44-
45-
it('should pass undefined to computeRestriction when both statsToken is omitted and findLastStatsToken returns undefined', async () => {
46-
(Statistics.findLastStatsToken as jest.Mock).mockResolvedValue(undefined);
47-
48-
await sendUsageReportAndComputeRestriction();
49-
50-
expect(Statistics.findLastStatsToken).toHaveBeenCalled();
51-
expect(AirGappedRestriction.computeRestriction).toHaveBeenCalledWith(undefined);
12+
it('should not compute air-gapped restrictions in the DevSH FOSS build', async () => {
13+
await expect(sendUsageReportAndComputeRestriction('test-token')).resolves.toBeUndefined();
5214
});
5315
});

apps/meteor/server/cron/usageReport.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
import { cronJobs } from '@rocket.chat/cron';
2-
import { AirGappedRestriction } from '@rocket.chat/license';
32
import type { Logger } from '@rocket.chat/logger';
4-
import { Statistics } from '@rocket.chat/models';
53

64
import { sendUsageReport } from '../../app/statistics/server/functions/sendUsageReport';
75

8-
export const sendUsageReportAndComputeRestriction = async (statsToken?: string) => {
9-
// If the report failed to be sent we need to get the last existing token
10-
// to ensure that the restriction respects the warning period.
11-
// If no token is passed, the workspace will be instantly restricted.
12-
const token = statsToken || (await Statistics.findLastStatsToken());
13-
void AirGappedRestriction.computeRestriction(token);
6+
export const sendUsageReportAndComputeRestriction = async (_statsToken?: string) => {
7+
return undefined;
148
};
159

1610
export const shouldReportStatistics = () => process.env.RC_DISABLE_STATISTICS_REPORTING?.toLowerCase() !== 'true';

0 commit comments

Comments
 (0)