Skip to content

Commit 4ce27cf

Browse files
Merge pull request #3498 from RedisInsight/codeql
Codeql
2 parents 06c9377 + 66ab995 commit 4ce27cf

File tree

6 files changed

+51
-13
lines changed

6 files changed

+51
-13
lines changed

redisinsight/api/src/__mocks__/custom-tutorial.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ export const mockCustomTutorialId2 = 'a77b23c1-7816-4ea4-b61f-d37795a0f805-ct-id
1111

1212
export const mockCustomTutorialTmpPath = '/tmp/path';
1313

14-
export const mockCustomTutorialsHttpLink = 'https://somesime.com/archive.zip';
14+
export const mockCustomTutorialsHttpLink = 'https://github.com/archive.zip';
15+
export const mockCustomTutorialsHttpLink2 = 'https://raw.githubusercontent.com/archive.zip';
1516

1617
export const mockCustomTutorial = Object.assign(new CustomTutorial(), {
1718
id: mockCustomTutorialId,

redisinsight/api/src/constants/error-messages.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export default {
1616
PLUGIN_STATE_NOT_FOUND: 'Plugin state was not found.',
1717
CUSTOM_TUTORIAL_NOT_FOUND: 'Custom Tutorial was not found.',
1818
CUSTOM_TUTORIAL_UNABLE_TO_FETCH_FROM_EXTERNAL: 'Unable fetch zip file from external source.',
19+
CUSTOM_TUTORIAL_UNSUPPORTED_ORIGIN: 'Unsupported origin for tutorial.',
1920
UNDEFINED_INSTANCE_ID: 'Undefined redis database instance id.',
2021
NO_CONNECTION_TO_REDIS_DB: 'No connection to the Redis Database.',
2122
WRONG_DATABASE_TYPE: 'Wrong database type.',

redisinsight/api/src/modules/custom-tutorial/dto/upload.custom-tutorial.dto.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
33
import {
44
HasMimeType, IsFile, MaxFileSize, MemoryStoredFile,
55
} from 'nestjs-form-data';
6-
import { IsGitHubLink } from 'src/common/decorators';
76

87
export class UploadCustomTutorialDto {
98
@ApiPropertyOptional({
@@ -24,6 +23,5 @@ export class UploadCustomTutorialDto {
2423
@IsOptional()
2524
@IsString()
2625
@IsNotEmpty()
27-
@IsGitHubLink()
2826
link?: string;
2927
}

redisinsight/api/src/modules/custom-tutorial/providers/custom-tutorial.fs.provider.spec.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import { Test, TestingModule } from '@nestjs/testing';
22
import {
33
mockCustomTutorial,
44
mockCustomTutorialAdmZipEntry,
5-
mockCustomTutorialMacosxAdmZipEntry, mockCustomTutorialsHttpLink,
5+
mockCustomTutorialMacosxAdmZipEntry, mockCustomTutorialsHttpLink, mockCustomTutorialsHttpLink2,
66
mockCustomTutorialTmpPath,
77
mockCustomTutorialZipFile, mockCustomTutorialZipFileAxiosResponse,
88
} from 'src/__mocks__';
99
import * as fs from 'fs-extra';
1010
import axios from 'axios';
1111
import { CustomTutorialFsProvider } from 'src/modules/custom-tutorial/providers/custom-tutorial.fs.provider';
12-
import { InternalServerErrorException } from '@nestjs/common';
12+
import { BadRequestException, InternalServerErrorException } from '@nestjs/common';
1313
import AdmZip from 'adm-zip';
1414
import ERROR_MESSAGES from 'src/constants/error-messages';
1515
import config from 'src/utils/config';
@@ -80,12 +80,24 @@ describe('CustomTutorialFsProvider', () => {
8080
});
8181

8282
describe('unzipFromExternalLink', () => {
83-
it('should unzip data from external link', async () => {
84-
const result = await service.unzipFromExternalLink(mockCustomTutorialsHttpLink);
83+
it.each([
84+
mockCustomTutorialsHttpLink,
85+
mockCustomTutorialsHttpLink2,
86+
])('should unzip data from external link', async (url) => {
87+
const result = await service.unzipFromExternalLink(url);
8588
expect(result).toEqual(mockCustomTutorialTmpPath);
8689
});
8790

88-
it('should throw InternalServerError when 4incorrect external link provided', async () => {
91+
it.each([
92+
'http://hithub.com',
93+
'http://raw.githubusercontent.com',
94+
'http://raw.amy.other.com',
95+
])('should unzip data from external link', async (url) => {
96+
await expect(service.unzipFromExternalLink(url))
97+
.rejects.toThrow(new BadRequestException(ERROR_MESSAGES.CUSTOM_TUTORIAL_UNSUPPORTED_ORIGIN));
98+
});
99+
100+
it('should throw InternalServerError when incorrect external link provided', async () => {
89101
const responsePayload = {
90102
response: {
91103
status: 404,

redisinsight/api/src/modules/custom-tutorial/providers/custom-tutorial.fs.provider.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { Injectable, InternalServerErrorException, Logger } from '@nestjs/common';
1+
import {
2+
BadRequestException, Injectable, InternalServerErrorException, Logger,
3+
} from '@nestjs/common';
24
import { MemoryStoredFile } from 'nestjs-form-data';
35
import { join } from 'path';
46
import { v4 as uuidv4 } from 'uuid';
@@ -13,6 +15,11 @@ const PATH_CONFIG = config.get('dir_path');
1315

1416
const TMP_FOLDER = `${PATH_CONFIG.tmpDir}/RedisInsight/custom-tutorials`;
1517

18+
const UPLOAD_FROM_REMOTE_ORIGINS_WHITELIST = [
19+
'https://github.com',
20+
'https://raw.githubusercontent.com',
21+
];
22+
1623
@Injectable()
1724
export class CustomTutorialFsProvider {
1825
private logger = new Logger('CustomTutorialFsProvider');
@@ -73,6 +80,12 @@ export class CustomTutorialFsProvider {
7380
*/
7481
public async unzipFromExternalLink(link: string): Promise<string> {
7582
try {
83+
const url = new URL(link);
84+
85+
if (!UPLOAD_FROM_REMOTE_ORIGINS_WHITELIST.includes(url.origin)) {
86+
return Promise.reject(new BadRequestException(ERROR_MESSAGES.CUSTOM_TUTORIAL_UNSUPPORTED_ORIGIN));
87+
}
88+
7689
const { data } = await axios.get(link, {
7790
responseType: 'arraybuffer',
7891
});

redisinsight/api/src/utils/hosting-provider-helper.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,39 @@ import { IP_ADDRESS_REGEX, PRIVATE_IP_ADDRESS_REGEX } from 'src/constants';
22
import { HostingProvider } from 'src/modules/database/entities/database.entity';
33
import { RedisClient } from 'src/modules/redis/client';
44

5+
const PROVIDER_HOST_REGEX = {
6+
RLCP: /\.rlrcp\.com$/,
7+
REDISLABS: /\.redislabs\.com$/,
8+
REDISCLOUD: /\.redis-cloud\.com$/,
9+
CACHE_AMAZONAWS: /cache\.amazonaws\.com$/,
10+
CACHE_WINDOWS: /cache\.windows\.net$/,
11+
RE_CACHE_AZURE: /redisenterprise\.cache\.azure\.net$/,
12+
};
13+
514
// Because we do not bind potentially dangerous logic to this.
615
// We define a hosting provider for telemetry only.
716
export const getHostingProvider = async (client: RedisClient, databaseHost: string): Promise<HostingProvider> => {
817
try {
918
const host = databaseHost.toLowerCase();
1019

1120
// Tries to detect the hosting provider from the hostname.
12-
if (host.endsWith('rlrcp.com') || host.endsWith('redislabs.com') || host.endsWith('redis-cloud.com')) {
21+
if (
22+
PROVIDER_HOST_REGEX.RLCP.test(host)
23+
|| PROVIDER_HOST_REGEX.REDISLABS.test(host)
24+
|| PROVIDER_HOST_REGEX.REDISCLOUD.test(host)
25+
) {
1326
return HostingProvider.RE_CLOUD;
1427
}
15-
if (host.endsWith('cache.amazonaws.com')) {
28+
if (PROVIDER_HOST_REGEX.CACHE_AMAZONAWS.test(host)) {
1629
return HostingProvider.AWS_ELASTICACHE;
1730
}
1831
if (host.includes('memorydb')) {
1932
return HostingProvider.AWS_MEMORYDB;
2033
}
21-
if (host.endsWith('cache.windows.net')) {
34+
if (PROVIDER_HOST_REGEX.CACHE_WINDOWS.test(host)) {
2235
return HostingProvider.AZURE_CACHE;
2336
}
24-
if (host.endsWith('redisenterprise.cache.azure.net')) {
37+
if (PROVIDER_HOST_REGEX.RE_CACHE_AZURE.test(host)) {
2538
return HostingProvider.AZURE_CACHE_REDIS_ENTERPRISE;
2639
}
2740

0 commit comments

Comments
 (0)