Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/app/views/common/share.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { parseSampleUrl } from '../../utils/sample-url-generation';
import { createShareLink } from './share';

jest.mock('../../../modules/authentication', () => ({
Expand Down Expand Up @@ -64,6 +65,35 @@ describe('createShareLink', () => {
expect(link).toContain('sid=test-session-id');
});

describe('when the sample url is malformed', () => {
// Use the real parser: the module-level mock above does not reproduce how it handles bad input.
const { parseSampleUrl: realParseSampleUrl } = jest.requireActual('../../utils/sample-url-generation');

const createShareLinkFor = (sampleUrl: string) => {
(parseSampleUrl as jest.Mock).mockImplementationOnce(() => realParseSampleUrl(sampleUrl));
return createShareLink({ ...baseQuery, sampleUrl } as any);
};

// The parser derives an origin of 'null' for an unknown scheme, which produces a url that cannot be parsed.
it.each([
'htps://graph.microsoft.com/v1.0/me',
'htttps://graph.microsoft.com/v1.0/me?$select=id'
])('falls back to the default graph url for a mistyped scheme: %s', (sampleUrl) => {
expect(realParseSampleUrl(sampleUrl).sampleUrl).toMatch(/^null\//);

const link = createShareLinkFor(sampleUrl);

expect(link).toContain('GraphUrl=https://graph.microsoft.com');
});

it.each([
'https//graph.microsoft.com/v1.0/me',
'graph.microsoft.com/v1.0/me'
])('returns an empty link without throwing when the parser cannot read the url: %s', (sampleUrl) => {
expect(createShareLinkFor(sampleUrl)).toBe('');
});
});

it('should not include session ID when not authenticated', () => {
const link = createShareLink(baseQuery as any, false);
expect(link).not.toContain('sid=');
Expand Down
11 changes: 9 additions & 2 deletions src/app/views/common/share.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,15 @@ export const createShareLink = (sampleQuery: IQuery, authenticated?: boolean): s
return '';
}

const url = new URL(sampleUrl);
const graphUrl = url.origin;
let graphUrl = 'https://graph.microsoft.com';

try {
const url = new URL(sampleUrl);
graphUrl = url.origin;
} catch {
// do nothing and use default graph url
Comment thread
claudio-codebase marked this conversation as resolved.
}

const appUrl = 'https://developer.microsoft.com/graph/graph-explorer';

const graphUrlRequest = encodeURIComponent(requestUrl + search);
Expand Down
1 change: 1 addition & 0 deletions src/messages/GE.json
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@
"Documentation": "Documentation",
"Query documentation": "Query documentation",
"This token is not a jwt token and cannot be decoded by jwt.ms": "This token is not a jwt token and cannot be decoded by jwt.ms",
"The URL must begin with https://": "The URL must begin with https://",
"The URL must contain graph.microsoft.com": "The URL must contain graph.microsoft.com",
"Something went wrong": "Something went wrong while downloading this tab. Please try again",
"Query documentation not found": "Query documentation not found",
Expand Down
25 changes: 25 additions & 0 deletions src/modules/validation/validation-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,31 @@ describe('ValidationService.validate edge cases', () => {
expect(() => ValidationService.validate('https://example.com/v1.0/me', [])).toThrow(ValidationError);
});

it.each([
'http://graph.microsoft.com/v1.0/me',
'htps://graph.microsoft.com/v1.0/me',
'https//graph.microsoft.com/v1.0/me',
'graph.microsoft.com/v1.0/me'
])('reports an error asking for https:// when the scheme is wrong: %s', (url) => {
expect(() => ValidationService.validate(url, [])).toThrow(
expect.objectContaining({ type: 'error', message: 'The URL must begin with https://' })
);
});

it.each([
'https://graph.microsft.com/v1.0/me',
'https://example.com/v1.0/me',
'https://'
])('reports an error asking for graph.microsoft.com when the host is wrong: %s', (url) => {
expect(() => ValidationService.validate(url, [])).toThrow(
expect.objectContaining({ type: 'error', message: 'The URL must contain graph.microsoft.com' })
);
});

it('does not throw for a valid https url', () => {
expect(() => ValidationService.validate('https://graph.microsoft.com/v1.0/me', [])).not.toThrow();
});

it('should throw error for missing version', () => {
expect(() => ValidationService.validate('https://graph.microsoft.com/', [])).toThrow(ValidationError);
});
Expand Down
17 changes: 15 additions & 2 deletions src/modules/validation/validation-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ import { ValidatedUrl } from './abnf';

class ValidationService {

private static getHostname(queryUrl: string): string {
try {
return new URL(queryUrl).hostname;
} catch {
return '';
}
}

private static getResourceValidationError(queryUrl: string, resources: IResource[]): string | null {
if (resources.length === 0) {
return null;
Expand Down Expand Up @@ -40,8 +48,13 @@ class ValidationService {
'error');
}

const { hostname } = new URL(queryUrl);
if (!isValidHostname(hostname)) {
if (!/^https:\/\//i.test(queryUrl.trim())) {
throw new ValidationError(
`${translateMessage('The URL must begin with https://')}`,
'error');
}

if (!isValidHostname(ValidationService.getHostname(queryUrl))) {
throw new ValidationError(
`${translateMessage('The URL must contain graph.microsoft.com')}`,
'error');
Expand Down
Loading