diff --git a/src/app/modelRepository/components/__snapshots__/listItemConfigurableRepo.spec.tsx.snap b/src/app/modelRepository/components/__snapshots__/listItemConfigurableRepo.spec.tsx.snap index e7d5836e..fe5e6316 100644 --- a/src/app/modelRepository/components/__snapshots__/listItemConfigurableRepo.spec.tsx.snap +++ b/src/app/modelRepository/components/__snapshots__/listItemConfigurableRepo.spec.tsx.snap @@ -15,16 +15,11 @@ exports[`ListItemConfigurableRepo matches snapshot 1`] = ` > modelRepository.types.configurable.infoText + + modelRepository.types.configurable.warning + - `; diff --git a/src/app/modelRepository/components/listItemConfigurableRepo.spec.tsx b/src/app/modelRepository/components/listItemConfigurableRepo.spec.tsx index fc9a4e83..72580fb4 100644 --- a/src/app/modelRepository/components/listItemConfigurableRepo.spec.tsx +++ b/src/app/modelRepository/components/listItemConfigurableRepo.spec.tsx @@ -26,23 +26,4 @@ describe('ListItemConfigurableRepo', () => { ); expect(wrapper).toMatchSnapshot(); }); - - it('calls actions with expected params', () => { - const setDirtyFlag = jest.fn(); - const setRepositoryLocationSettings = jest.fn(); - const wrapper = shallow( - - ); - act(() => wrapper.find(TextField).props().onChange?.(undefined as any, 'test.com')); - expect(setDirtyFlag).toBeCalledWith(true); - expect(setRepositoryLocationSettings).toBeCalledWith([{repositoryLocationType: REPOSITORY_LOCATION_TYPE.Configurable, value: 'test.com'}]) - }); }); diff --git a/src/app/modelRepository/components/listItemConfigurableRepo.tsx b/src/app/modelRepository/components/listItemConfigurableRepo.tsx index 3153732d..6bbee77b 100644 --- a/src/app/modelRepository/components/listItemConfigurableRepo.tsx +++ b/src/app/modelRepository/components/listItemConfigurableRepo.tsx @@ -4,7 +4,7 @@ **********************************************************/ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { TextField } from '@fluentui/react'; +import { MessageBar, MessageBarType } from '@fluentui/react'; import { REPOSITORY_LOCATION_TYPE } from '../../constants/repositoryLocationTypes'; import { ResourceKeys } from '../../../localization/resourceKeys'; import { ModelRepositoryConfiguration } from '../../shared/modelRepository/state'; @@ -18,55 +18,21 @@ export interface ListItemConfigurableRepoProps{ export const ListItemConfigurableRepo: React.FC = ({index, item, formState}) => { const { t } = useTranslation(); - const [{repositoryLocationSettings, repositoryLocationSettingsErrors }, {setRepositoryLocationSettings, setDirtyFlag}] = formState; - const errorKey = repositoryLocationSettingsErrors[item.repositoryLocationType]; let initialConfigurableRepositoryPath = ''; if (item && item.repositoryLocationType === REPOSITORY_LOCATION_TYPE.Configurable) { initialConfigurableRepositoryPath = item.value; } - const [ currentConfigurableRepositoryPath, setCurrentConfigurableRepositoryPath ] = React.useState(initialConfigurableRepositoryPath); - - React.useEffect(() => { - setCurrentConfigurableRepositoryPath(initialConfigurableRepositoryPath); - }, [initialConfigurableRepositoryPath]); // tslint:disable-line: align - - const onChangeRepositoryLocationSettingValue = (value: string) => { - const updatedRepositoryLocationSettings = repositoryLocationSettings.map((setting, i) => { - if (i === index) { - const updatedSetting = {...setting}; - updatedSetting.value = value; - return updatedSetting; - } else { - return setting; - } - }); - - setDirtyFlag(true); - setRepositoryLocationSettings(updatedRepositoryLocationSettings); - }; - - const repositoryEndpointChange = (event: React.FormEvent, newValue?: string) => { - setCurrentConfigurableRepositoryPath(newValue); - onChangeRepositoryLocationSettingValue(newValue); - }; return( <> {t(ResourceKeys.modelRepository.types.configurable.label)} {t(ResourceKeys.modelRepository.types.configurable.infoText)} + + {t(ResourceKeys.modelRepository.types.configurable.warning)} + - > ); }; diff --git a/src/localization/locales/en.json b/src/localization/locales/en.json index cb3fd8c2..6ef529df 100644 --- a/src/localization/locales/en.json +++ b/src/localization/locales/en.json @@ -972,6 +972,7 @@ "configurable" : { "label": "Configurable Repository", "infoText": "Configure your own repository endpoint.", + "warning": "For security reasons, configurable repository is no longer supported.", "textBoxLabel": "Repository endpoint" }, "local": { diff --git a/src/localization/resourceKeys.ts b/src/localization/resourceKeys.ts index 5158ce2e..42a274fd 100644 --- a/src/localization/resourceKeys.ts +++ b/src/localization/resourceKeys.ts @@ -834,6 +834,7 @@ export class ResourceKeys { infoText : "modelRepository.types.configurable.infoText", label : "modelRepository.types.configurable.label", textBoxLabel : "modelRepository.types.configurable.textBoxLabel", + warning : "modelRepository.types.configurable.warning", }, dmr : { infoText : "modelRepository.types.dmr.infoText", diff --git a/src/server/serverBase.spec.ts b/src/server/serverBase.spec.ts index 7b8ceedc..e165f419 100644 --- a/src/server/serverBase.spec.ts +++ b/src/server/serverBase.spec.ts @@ -37,20 +37,4 @@ describe('serverBase', () => { expect(res.status).toHaveBeenCalledWith(400); // tslint:disable-line:no-magic-numbers }); }); - - context('handleModelRepoPostRequest', () => { - it('returns 400 if body is not provided', async () => { - const req = mockRequest(); - const res = mockResponse(); - await ServerBase.handleModelRepoPostRequest(req, res); - expect(res.status).toHaveBeenCalledWith(400); // tslint:disable-line:no-magic-numbers - }); - - it('returns 500 if response is error', async () => { - const req = mockRequest({url: 'test'}); - const res = mockResponse(); - await ServerBase.handleModelRepoPostRequest(req, res); - expect(res.status).toHaveBeenCalledWith(403); // tslint:disable-line:no-magic-numbers - }); - }); }); \ No newline at end of file diff --git a/src/server/serverBase.ts b/src/server/serverBase.ts index 9088562e..39bd9a0c 100644 --- a/src/server/serverBase.ts +++ b/src/server/serverBase.ts @@ -14,7 +14,7 @@ import * as he from 'he'; import { EventHubConsumerClient, Subscription, ReceivedEventData, earliestEventPosition } from '@azure/event-hubs'; import { generateDataPlaneRequestBody, generateDataPlaneResponse } from './dataPlaneHelper'; import { convertIotHubToEventHubsConnectionString } from './eventHubHelper'; -import { fetchDirectories, findMatchingFile, isSafeUrl, readFileFromLocal, SAFE_ROOT } from './utils'; +import { fetchDirectories, findMatchingFile, readFileFromLocal, SAFE_ROOT } from './utils'; export const SERVER_ERROR = 500; export const SUCCESS = 200; @@ -52,7 +52,6 @@ export class ServerBase { app.post(dataPlaneUri, handleDataPlanePostRequest); app.post(eventHubMonitorUri, handleEventHubMonitorPostRequest); app.post(eventHubStopUri, handleEventHubStopPostRequest); - app.post(modelRepoUri, handleModelRepoPostRequest); app.get(readFileUri, handleReadFileRequest); app.get(readFileNaiveUri, handleReadFileNaiveRequest); app.get(getDirectoriesUri, handleGetDirectoriesRequest); @@ -198,38 +197,6 @@ export const handleEventHubStopPostRequest = (req: express.Request, res: express } }; -const modelRepoUri = '/api/ModelRepo'; -export const handleModelRepoPostRequest = async (req: express.Request, res: express.Response) => { - const controllerRequest = req.body; - const userUri = controllerRequest?.uri; - const ALLOWED_DOMAINS = ["github.com", "bitbucket.org", "azure.com"]; - - const isAllowedDomain = (url: string) => { - try { - const parsedUrl = new URL(url); - return ALLOWED_DOMAINS.includes(parsedUrl.hostname); - } catch { - return false; // Invalid URL - } - }; - - if (!isAllowedDomain(userUri)) { - return res.status(403).send({ error: "Forbidden: Unsafe URL." }); - } - - try { - const response = await fetch(userUri, - { - body: controllerRequest.body || null, - headers: controllerRequest.headers || null, - method: controllerRequest.method || 'GET', - }); - res.status((response && response.status) || SUCCESS).send(await response.json() || {}); //tslint:disable-line - } catch (error) { - res.status(SERVER_ERROR).send(he.encode(error.toString())); - } -}; - const initializeEventHubClient = async (params: any) => { if (params.customEventHubConnectionString) { client = new EventHubConsumerClient(params.consumerGroup, params.customEventHubConnectionString); diff --git a/src/server/utils.ts b/src/server/utils.ts index fdcf5be4..02dd10d7 100644 --- a/src/server/utils.ts +++ b/src/server/utils.ts @@ -1,7 +1,6 @@ import express = require('express'); import * as fs from 'fs'; import * as path from 'path'; -import * as dns from 'dns'; var escape = require('escape-html'); import { SERVER_ERROR, SUCCESS } from './serverBase'; @@ -103,38 +102,3 @@ const isFileExtensionJson = (fileName: string) => { export const readFileFromLocal = (filePath: string, fileName: string) => { return fs.readFileSync(`${filePath}/${fileName}`, 'utf-8'); } - -export const isSafeUrl = async (userUrl: string): Promise => { - try { - const parsedUrl = new URL(userUrl); - - // Enforce HTTPS - if (parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') { - return false; - } - - // Resolve DNS to check IP addresses - const addresses = await dns.promises.resolve(parsedUrl.hostname); - - // Block local and private IPs - for (const address of addresses) { - if ( - address.startsWith('127.') || // Loopback - address.startsWith('10.') || // Private - address.startsWith('192.168.') || // Private - address.startsWith('169.254.') || // Link-local - address === '0.0.0.0' || // Unspecified - address === '::1' || // IPv6 loopback - address.startsWith('fe80:') || // IPv6 link-local - address.startsWith('fc00:') || // IPv6 unique local - address.startsWith('fd00:') // IPv6 unique local - ) { - return false; - } - } - - return true; - } catch { - return false; - } -}; \ No newline at end of file