Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore factory URL parameters after redirect #906

Merged
merged 7 commits into from
Sep 4, 2023
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
80 changes: 80 additions & 0 deletions packages/common/src/helpers/__tests__/sanitize.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2018-2023 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/

import {
sanitizeLocation,
sanitizeSearchParams,
sanitizePathname,
} from '../sanitize';

describe('sanitize', () => {
afterEach(() => {
jest.clearAllMocks();
});

describe('sanitizeLocation', () => {
it('sanitizeLocation', () => {
const location = {
search:
'url=https%3A%2F%2Fgithub.com%2Ftest-samples&storageType=persistent',
pathname: '/f',
};

const sanitizedLocation = sanitizeLocation(location);

expect(sanitizedLocation).toEqual(
expect.objectContaining({
search:
'url=https%3A%2F%2Fgithub.com%2Ftest-samples&storageType=persistent',
pathname: '/f',
}),
);
});
});

describe('sanitizeSearchParams', () => {
it('should return sanitized value of location.search if it is without encoding)', () => {
const search =
'url=https://github.com/test-samples&state=9284564475&session=98765&session_state=45645654567&code=9844646765&storageType=persistent&new';

const searchParams = new URLSearchParams(search);
const sanitizedSearchParams = sanitizeSearchParams(searchParams);

expect(sanitizedSearchParams.toString()).toEqual(
'url=https%3A%2F%2Fgithub.com%2Ftest-samples&storageType=persistent&new=',
);
});

it('should return sanitized value of location.search if it is encoded', () => {
const search =
'url=https%3A%2F%2Fgithub.com%2Ftest-samples%26state%3D9284564475%26session%3D98765%26session_state%3D45645654567%26code%3D9844646765%26storageType%3Dpersistent';

const searchParams = new URLSearchParams(search);
const sanitizedSearchParams = sanitizeSearchParams(searchParams);

expect(sanitizedSearchParams.toString()).toEqual(
'url=https%3A%2F%2Fgithub.com%2Ftest-samples%26storageType%3Dpersistent',
);
});
});

describe('sanitizePathname', () => {
it('should remove oauth redirect leftovers', () => {
const pathname =
'/f&code=12345&session=67890&session_state=13579&state=24680';

const newPathname = sanitizePathname(pathname);

expect(newPathname).toEqual('/f');
});
});
});
8 changes: 8 additions & 0 deletions packages/common/src/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,15 @@
*/

import * as errors from './errors';
import {
sanitizeLocation,
sanitizePathname,
sanitizeSearchParams,
} from './sanitize';

export default {
errors,
sanitizeLocation,
sanitizePathname,
sanitizeSearchParams,
};
78 changes: 78 additions & 0 deletions packages/common/src/helpers/sanitize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright (c) 2018-2023 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/

const oauthParams = ['state', 'session', 'session_state', 'code'];

/**
* Remove oauth params.
*/
export function sanitizeLocation<
T extends { search: string; pathname: string } = Location,
>(location: T, removeParams: string[] = []): T {
const sanitizedSearchParams = sanitizeSearchParams(
new URLSearchParams(location.search),
removeParams,
);
const sanitizedPathname = sanitizePathname(location.pathname, removeParams);

return {
...location,
search: sanitizedSearchParams.toString(),
searchParams: sanitizedSearchParams,
pathname: sanitizedPathname,
};
}

export function sanitizeSearchParams(
searchParams: URLSearchParams,
removeParams: string[] = [],
): URLSearchParams {
const toRemove = [...oauthParams, ...removeParams];

// remove oauth params
toRemove.forEach(val => searchParams.delete(val));

// sanitize each query param
const sanitizedSearchParams = new URLSearchParams();
searchParams.forEach((value, param) => {
if (!value) {
sanitizedSearchParams.set(param, value);
return;
}

const sanitizedValue = sanitizeStr(value, toRemove);
sanitizedSearchParams.set(param, sanitizedValue);
});

return sanitizedSearchParams;
}

export function sanitizePathname(
pathname: string,
removeParams: string[] = [],
): string {
const toRemove = [...oauthParams, ...removeParams];

// sanitize pathname
const sanitizedPathname = sanitizeStr(pathname, toRemove);

return sanitizedPathname;
}

function sanitizeStr(str: string, removeParams: string[] = []): string {
removeParams.forEach(param => {
const re = new RegExp('&' + param + '=.+?(?=(?:[?&/#]|$))', 'i');
str = str.replace(re, '');
});

return str;
}
2 changes: 2 additions & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export * from './dto/cluster-config';

export { helpers, api };

export const FACTORY_LINK_ATTR = 'factoryLink';

const common = {
helpers,
api,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ describe('Factory Acceptance Redirect', () => {
expect(res.headers.location).toEqual(`/dashboard/#/load-factory?url=${factoryUrl}`);
});

it('should redirect "/f?url=factoryUrl"', async () => {
const factoryUrl = 'factoryUrl';
const res = await app.inject({
url: `/f?factoryLink=${encodeURIComponent('url=' + factoryUrl)}`,
});
expect(res.statusCode).toEqual(302);
expect(res.headers.location).toEqual(`/dashboard/#/load-factory?url=${factoryUrl}`);
});

it('should redirect "/dashboard/f?url=factoryUrl"', async () => {
const factoryUrl = 'factoryUrl';
const res = await app.inject({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,27 @@
* Red Hat, Inc. - initial API and implementation
*/

import { FACTORY_LINK_ATTR } from '@eclipse-che/common';
import { sanitizeSearchParams } from '@eclipse-che/common/src/helpers/sanitize';
import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import querystring from 'querystring';

export function registerFactoryAcceptanceRedirect(instance: FastifyInstance): void {
// redirect to the Dashboard factory flow
function redirectFactoryFlow(path: string) {
instance.register(async server => {
server.get(path, async (request: FastifyRequest, reply: FastifyReply) => {
const queryStr = request.url.replace(path, '');
return reply.redirect('/dashboard/#/load-factory' + queryStr);
let queryStr = request.url.replace(path, '').replace(/^\?/, '');

const query = querystring.parse(queryStr);
if (query[FACTORY_LINK_ATTR] !== undefined) {
// restore the factory link from the query string
queryStr = querystring.unescape(query[FACTORY_LINK_ATTR] as string);
}

const sanitizedQueryParams = sanitizeSearchParams(new URLSearchParams(queryStr));

return reply.redirect('/dashboard/#/load-factory?' + sanitizedQueryParams.toString());
});
});
}
Expand Down
4 changes: 2 additions & 2 deletions packages/dashboard-frontend/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ module.exports = {
],
coverageThreshold: {
global: {
statements: 80,
statements: 81,
branches: 85,
functions: 77,
lines: 80,
lines: 81,
},
},
};
2 changes: 1 addition & 1 deletion packages/dashboard-frontend/src/Routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import React from 'react';
import { Redirect, Route, RouteComponentProps, Switch } from 'react-router';
import { buildFactoryLoaderPath } from '../preload';
import { buildFactoryLoaderPath } from '../preload/main';
import { ROUTE } from './routes';

const CreateWorkspace = React.lazy(() => import('../pages/GetStarted'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function getProjectFromLocation(
git: { remotes: { [remoteName]: origin } },
name,
};
} else if (FactoryLocationAdapter.isFullPathUrl(location)) {
} else if (FactoryLocationAdapter.isHttpLocation(location)) {
const sourceUrl = new URL(location);
if (sourceUrl.pathname.endsWith('.git')) {
const origin = `${sourceUrl.origin}${sourceUrl.pathname}`;
Expand Down
Loading
Loading