Skip to content

Commit 72a9a9c

Browse files
Feature/ri 6881 disable db management (#4428)
* RI-6881 initial implementation (API) * RI-6881 rename RI_READONLY_CONNECTIONS to RI_DATABASE_MANAGEMENT to avoid misunderstandings * RI-6881 fix guard behavior * RI-6881 remove console.log * RI-6881 add tests * RI-6881 fix ITests * RI-6881 fix cloud jobs conditions * RI-6881 hide db CRUD buttons (except cloud) (#4429) * RI-6881 hide db CRUD buttons (except cloud) * RI-6881 add custom error handler + analytics
1 parent 280c5fb commit 72a9a9c

File tree

47 files changed

+964
-38
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+964
-38
lines changed

redisinsight/api/config/default.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export default {
8888
requestTimeout: parseInt(process.env.RI_REQUEST_TIMEOUT, 10) || 25000,
8989
excludeRoutes: [],
9090
excludeAuthRoutes: [],
91+
databaseManagement: process.env.RI_DATABASE_MANAGEMENT !== 'false',
9192
},
9293
statics: {
9394
initDefaults: process.env.RI_STATICS_INIT_DEFAULTS ? process.env.RI_STATICS_INIT_DEFAULTS === 'true' : true,

redisinsight/api/src/__mocks__/database-import.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,7 @@ export const mockSshImportService = jest.fn(() => ({
9595
id: undefined,
9696
}),
9797
}));
98+
99+
export const mockDatabaseImportService = jest.fn(() => ({
100+
import: jest.fn(),
101+
}));

redisinsight/api/src/__mocks__/databases.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { CloudDatabaseDetailsEntity } from 'src/modules/cloud/database/entities/
1818
import { mockCloudDatabaseDetails, mockCloudDatabaseDetailsEntity } from 'src/__mocks__/cloud-database';
1919
import { mockRedisClientListResult } from 'src/__mocks__/database-info';
2020
import { DatabaseOverviewKeyspace } from 'src/modules/database/constants/overview';
21+
import { CreateDatabaseDto } from 'src/modules/database/dto/create.database.dto';
2122

2223
export const mockDatabaseId = 'a77b23c1-7816-4ea4-b61f-d37795a0f805-db-id';
2324

@@ -43,6 +44,12 @@ export const mockDatabase = Object.assign(new Database(), {
4344
version: '7.0',
4445
});
4546

47+
export const mockCreateDatabaseDto = Object.assign(new CreateDatabaseDto(), {
48+
name: mockDatabase.name,
49+
host: mockDatabase.host,
50+
port: mockDatabase.port,
51+
});
52+
4653
export const mockDatabaseModules = [
4754
{
4855
name: 'rg',
@@ -267,6 +274,11 @@ export const mockDatabaseRepository = jest.fn(() => ({
267274
export const mockDatabaseService = jest.fn(() => ({
268275
get: jest.fn().mockResolvedValue(mockDatabase),
269276
create: jest.fn().mockResolvedValue(mockDatabase),
277+
update: jest.fn().mockResolvedValue(mockDatabase),
278+
clone: jest.fn().mockResolvedValue(mockDatabase),
279+
testConnection: jest.fn().mockResolvedValue(undefined),
280+
delete: jest.fn().mockResolvedValue(undefined),
281+
bulkDelete: jest.fn().mockResolvedValue({ affected: 0 }),
270282
list: jest.fn(),
271283
}));
272284

redisinsight/api/src/__mocks__/feature.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ export const mockFeatureSso = Object.assign(new Feature(), {
184184
},
185185
});
186186

187+
export const mockFeatureDatabaseManagement = Object.assign(new Feature(), {
188+
name: KnownFeatures.DatabaseManagement,
189+
flag: true,
190+
});
191+
187192
export const mockFeatureRedisClient = Object.assign(new Feature(), {
188193
name: KnownFeatures.RedisClient,
189194
flag: true,

redisinsight/api/src/__mocks__/redis-enterprise.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { RedisEnterpriseDatabase } from 'src/modules/redis-enterprise/dto/cluster.dto';
22
import { RedisEnterpriseDatabaseStatus } from 'src/modules/redis-enterprise/models/redis-enterprise-database';
3+
import { AddRedisEnterpriseDatabasesDto } from 'src/modules/redis-enterprise/dto/redis-enterprise-cluster.dto';
34

45
export const mockRedisEnterpriseDatabaseDto: RedisEnterpriseDatabase = {
56
uid: 1,
@@ -14,7 +15,19 @@ export const mockRedisEnterpriseDatabaseDto: RedisEnterpriseDatabase = {
1415
password: null,
1516
};
1617

18+
export const mockAddRedisEnterpriseDatabasesDto = Object.assign(new AddRedisEnterpriseDatabasesDto(), {
19+
host: 'localhost',
20+
port: 9443,
21+
username: 'admin',
22+
password: 'password',
23+
uids: [1],
24+
});
25+
1726
export const mockRedisEnterpriseAnalytics = jest.fn(() => ({
1827
sendGetREClusterDbsSucceedEvent: jest.fn(),
1928
sendGetREClusterDbsFailedEvent: jest.fn(),
2029
}));
30+
31+
export const mockRedisEnterpriseService = jest.fn(() => ({
32+
addRedisEnterpriseDatabases: jest.fn().mockResolvedValue([]),
33+
}));

redisinsight/api/src/__mocks__/redis-sentinel.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { SentinelMaster, SentinelMasterStatus } from 'src/modules/redis-sentinel/models/sentinel-master';
22
import { Endpoint } from 'src/common/models';
3+
import { CreateSentinelDatabasesDto } from 'src/modules/redis-sentinel/dto/create.sentinel.databases.dto';
34

45
export const mockOtherSentinelsReply = [[
56
'ip',
@@ -27,7 +28,22 @@ export const mockSentinelMasterDto: SentinelMaster = {
2728
nodes: [mockOtherSentinelEndpoint],
2829
};
2930

31+
export const mockCreateSentinelDatabasesDto = Object.assign(new CreateSentinelDatabasesDto(), {
32+
...mockOtherSentinelEndpoint,
33+
masters: [
34+
{
35+
name: mockSentinelMasterDto.name,
36+
alias: mockSentinelMasterDto.name,
37+
},
38+
],
39+
});
40+
3041
export const mockRedisSentinelAnalytics = jest.fn(() => ({
3142
sendGetSentinelMastersSucceedEvent: jest.fn(),
3243
sendGetSentinelMastersFailedEvent: jest.fn(),
3344
}));
45+
46+
export const mockRedisSentinelService = jest.fn(() => ({
47+
getSentinelMasters: jest.fn().mockResolvedValue([mockSentinelMasterDto]),
48+
createSentinelDatabases: jest.fn().mockResolvedValue([]),
49+
}));
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { applyDecorators, UseGuards } from '@nestjs/common';
2+
import { DatabaseManagementGuard } from 'src/common/guards/database-management.guard';
3+
4+
export function DatabaseManagement() {
5+
return applyDecorators(
6+
UseGuards(new DatabaseManagementGuard()),
7+
);
8+
}

redisinsight/api/src/common/decorators/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export * from './object-as-map.decorator';
88
export * from './is-multi-number.decorator';
99
export * from './is-bigger-than.decorator';
1010
export * from './is-github-link.decorator';
11+
export * from './database-management.decorator';
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { when } from 'jest-when';
2+
import { DatabaseManagementGuard } from 'src/common/guards/database-management.guard';
3+
import { ForbiddenException } from '@nestjs/common';
4+
import config, { Config } from 'src/utils/config';
5+
6+
const mockServerConfig = config.get('server') as Config['server'];
7+
8+
jest.mock('src/utils/config', jest.fn(
9+
() => jest.requireActual('src/utils/config') as object,
10+
));
11+
12+
describe('DatabaseManagementGuard', () => {
13+
let guard: DatabaseManagementGuard;
14+
let configGetSpy: jest.SpyInstance;
15+
16+
beforeEach(() => {
17+
jest.clearAllMocks();
18+
configGetSpy = jest.spyOn(config, 'get');
19+
20+
when(configGetSpy).calledWith('server').mockReturnValue(mockServerConfig);
21+
22+
guard = new DatabaseManagementGuard();
23+
});
24+
25+
it('should return true', () => {
26+
mockServerConfig.databaseManagement = true;
27+
28+
expect(guard.canActivate()).toEqual(true);
29+
});
30+
31+
it('should throw an error when database management is disabled', () => {
32+
mockServerConfig.databaseManagement = false;
33+
34+
expect(guard.canActivate).toThrowError(new ForbiddenException('Database connection management is disabled.'));
35+
});
36+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { CanActivate, ForbiddenException, Injectable } from '@nestjs/common';
2+
import config, { Config } from 'src/utils/config';
3+
import ERROR_MESSAGES from 'src/constants/error-messages';
4+
5+
const SERVER_CONFIG = config.get('server') as Config['server'];
6+
7+
@Injectable()
8+
export class DatabaseManagementGuard implements CanActivate {
9+
canActivate(): boolean {
10+
if (!SERVER_CONFIG.databaseManagement) {
11+
throw new ForbiddenException(ERROR_MESSAGES.DATABASE_MANAGEMENT_IS_DISABLED);
12+
}
13+
14+
return true;
15+
}
16+
}

0 commit comments

Comments
 (0)