Skip to content

Commit

Permalink
final fix
Browse files Browse the repository at this point in the history
  • Loading branch information
YingXue committed Feb 7, 2025
1 parent 4f09247 commit d3fd0f4
Show file tree
Hide file tree
Showing 8 changed files with 12 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,11 @@ exports[`ListItemConfigurableRepo matches snapshot 1`] = `
>
modelRepository.types.configurable.infoText
</div>
<StyledMessageBar
messageBarType={5}
>
modelRepository.types.configurable.warning
</StyledMessageBar>
</div>
<StyledTextFieldBase
ariaLabel="modelRepository.types.configurable.textBoxLabel"
className="local-folder-textbox"
errorMessage=""
label="modelRepository.types.configurable.textBoxLabel"
onChange={[Function]}
prefix="https://"
readOnly={false}
value="test.com"
/>
</Fragment>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<ListItemConfigurableRepo
index={0}
item={{
repositoryLocationType: REPOSITORY_LOCATION_TYPE.Configurable,
value: 'test.com'
}}
formState={[{...getInitialModelRepositoryFormState(), repositoryLocationSettings: [{repositoryLocationType: REPOSITORY_LOCATION_TYPE.Configurable, value: 'old.com'}]},
{...getInitialModelRepositoryFormOps(), setDirtyFlag, setRepositoryLocationSettings}]}
/>
);
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'}])
});
});
42 changes: 4 additions & 38 deletions src/app/modelRepository/components/listItemConfigurableRepo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -18,55 +18,21 @@ export interface ListItemConfigurableRepoProps{

export const ListItemConfigurableRepo: React.FC<ListItemConfigurableRepoProps> = ({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<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
setCurrentConfigurableRepositoryPath(newValue);
onChangeRepositoryLocationSettingValue(newValue);
};

return(
<>
<div className="labelSection">
<div className="label">{t(ResourceKeys.modelRepository.types.configurable.label)}</div>
<div className="description">{t(ResourceKeys.modelRepository.types.configurable.infoText)}</div>
<MessageBar messageBarType={MessageBarType.warning}>
{t(ResourceKeys.modelRepository.types.configurable.warning)}
</MessageBar>
</div>
<TextField
className="local-folder-textbox"
label={t(ResourceKeys.modelRepository.types.configurable.textBoxLabel)}
ariaLabel={t(ResourceKeys.modelRepository.types.configurable.textBoxLabel)}
value={currentConfigurableRepositoryPath}
readOnly={false}
errorMessage={errorKey ? t(errorKey) : ''}
onChange={repositoryEndpointChange}
prefix="https://"
/>
</>
);
};
1 change: 1 addition & 0 deletions src/localization/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
1 change: 1 addition & 0 deletions src/localization/resourceKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
16 changes: 0 additions & 16 deletions src/server/serverBase.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
});
});
35 changes: 1 addition & 34 deletions src/server/serverBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
36 changes: 0 additions & 36 deletions src/server/utils.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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<boolean> => {
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;
}
};

0 comments on commit d3fd0f4

Please sign in to comment.