Skip to content

Commit

Permalink
Stop redirecting to non-origin sites (#3710)
Browse files Browse the repository at this point in the history
* Stop directing to non-origin sites

* Create tall-mugs-shop.md
  • Loading branch information
zwu52 committed Sep 1, 2020
1 parent caed9c0 commit dc98925
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .changeset/tall-mugs-shop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@firebase/messaging': patch
---

stops redirecting user to non-origin urls.
34 changes: 29 additions & 5 deletions packages/messaging/src/controllers/sw-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ import { expect } from 'chai';
import { getFakeFirebaseDependencies } from '../testing/fakes/firebase-dependencies';
import { getFakeTokenDetails } from '../testing/fakes/token-details';

const LOCAL_HOST = self.location.host;
const TEST_LINK = 'https://' + LOCAL_HOST + '/test-link.org';
const TEST_CLICK_ACTION = 'https://' + LOCAL_HOST + '/test-click-action.org';

// Add fake SW types.
declare const self: Window & Writable<ServiceWorkerGlobalScope>;

Expand All @@ -59,7 +63,7 @@ const DISPLAY_MESSAGE: MessagePayloadInternal = {
body: 'body'
},
fcmOptions: {
link: 'https://example.org'
link: TEST_LINK
},
from: 'from',
// eslint-disable-next-line camelcase
Expand Down Expand Up @@ -454,9 +458,29 @@ describe('SwController', () => {
expect(matchAllSpy).not.to.have.been.called;
});

it('does not redirect if link is not from origin', async () => {
// Remove link.
NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG].fcmOptions.link =
'https://www.youtube.com';

const event = makeEvent('notificationclick', NOTIFICATION_CLICK_PAYLOAD);
const stopImmediatePropagationSpy = spy(
event,
'stopImmediatePropagation'
);
const notificationCloseSpy = spy(event.notification, 'close');
const matchAllSpy = spy(self.clients, 'matchAll');

await callEventListener(event);

expect(stopImmediatePropagationSpy).to.have.been.called;
expect(notificationCloseSpy).to.have.been.called;
expect(matchAllSpy).not.to.have.been.called;
});

it('focuses on and sends the message to an open WindowClient', async () => {
const client: Writable<WindowClient> = (await self.clients.openWindow(
'https://example.org'
TEST_LINK
))!;
const focusSpy = spy(client, 'focus');
const matchAllSpy = spy(self.clients, 'matchAll');
Expand Down Expand Up @@ -485,15 +509,15 @@ describe('SwController', () => {
await callEventListener(event);

expect(matchAllSpy).to.have.been.called;
expect(openWindowSpy).to.have.been.calledWith('https://example.org');
expect(openWindowSpy).to.have.been.calledWith(TEST_LINK);
});

it('works with click_action', async () => {
// Replace link with the deprecated click_action.
delete NOTIFICATION_CLICK_PAYLOAD.notification!.data![FCM_MSG].fcmOptions;
NOTIFICATION_CLICK_PAYLOAD.notification!.data![
FCM_MSG
].notification.click_action = 'https://example.org'; // eslint-disable-line camelcase
].notification.click_action = TEST_CLICK_ACTION; // eslint-disable-line camelcase

const matchAllSpy = spy(self.clients, 'matchAll');
const openWindowSpy = spy(self.clients, 'openWindow');
Expand All @@ -503,7 +527,7 @@ describe('SwController', () => {
await callEventListener(event);

expect(matchAllSpy).to.have.been.called;
expect(openWindowSpy).to.have.been.calledWith('https://example.org');
expect(openWindowSpy).to.have.been.calledWith(TEST_CLICK_ACTION);
});

it('redirects to origin if message was sent from the FN Console', async () => {
Expand Down
25 changes: 16 additions & 9 deletions packages/messaging/src/controllers/sw-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,15 +246,25 @@ export class SwController implements FirebaseMessaging, FirebaseService {
event.stopImmediatePropagation();
event.notification.close();

// Note clicking on a notification with no link set will focus the Chrome's current tab.
const link = getLink(internalPayload);
if (!link) {
return;
}

let client = await getWindowClient(link);
// FM should only open/focus links from app's origin.
const url = new URL(link, self.location.href);
const originUrl = new URL(self.location.origin);

if (url.host !== originUrl.host) {
return;
}

let client = await getWindowClient(url);

if (!client) {
// Unable to find window client so need to open one. This also focuses the opened client.
client = await self.clients.openWindow(link);

// Wait three seconds for the client to initialize and set up the message handler so that it
// can receive the message.
await sleep(3000);
Expand Down Expand Up @@ -309,16 +319,13 @@ function getMessagePayloadInternal({
* @param url The URL to look for when focusing a client.
* @return Returns an existing window client or a newly opened WindowClient.
*/
async function getWindowClient(url: string): Promise<WindowClient | null> {
// Use URL to normalize the URL when comparing to windowClients. This at least handles whether to
// include trailing slashes or not
const parsedURL = new URL(url, self.location.href);

async function getWindowClient(url: URL): Promise<WindowClient | null> {
const clientList = await getClientList();

for (const client of clientList) {
const parsedClientUrl = new URL(client.url, self.location.href);
if (parsedClientUrl.host === parsedURL.host) {
const clientUrl = new URL(client.url, self.location.href);

if (url.host === clientUrl.host) {
return client;
}
}
Expand Down

0 comments on commit dc98925

Please sign in to comment.