Skip to content

Commit

Permalink
Restore factory URL parameters after redirect (#906)
Browse files Browse the repository at this point in the history
  • Loading branch information
akurinnoy authored Sep 4, 2023
1 parent 14811c9 commit bb580b4
Show file tree
Hide file tree
Showing 22 changed files with 619 additions and 420 deletions.
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
16 changes: 14 additions & 2 deletions packages/dashboard-backend/src/routes/factoryAcceptanceRedirect.ts
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

0 comments on commit bb580b4

Please sign in to comment.