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

GH-1477 Wildcard/Regex Whitelisting #501

Merged
merged 8 commits into from Mar 3, 2020
Next

Add regex/wildcard listing with unit tests. Add safe-regex and jest-w…

…hen dependency
  • Loading branch information
benstrumeyer committed Feb 21, 2020
commit 17381a6cb519d1e20293b70db00fa8f475a5c08e
@@ -802,7 +802,7 @@
"message": "unblocked"
},
"white_black_list_error_invalid_url": {
"message": "Please enter a valid URL."
"message": "Please enter a valid URL, regex, or wildcard."
},
"whitelist_error_blacklist_url": {
"message": "This site has been removed from your Restricted Sites list and added to your Trusted Sites list."
@@ -1058,7 +1058,7 @@
"message": "Trusted Sites"
},
"settings_sites_placeholder": {
"message": "example.com"
"message": "example.com (wildcards/regex supported)"
},
"settings_restricted_sites": {
"message": "Restricted Sites"
@@ -12,6 +12,7 @@
*/

import React from 'react';
import safe from 'safe-regex';
import Sites from './Sites';
/**
* @class Implement Trust and Restrict subview presenting the lists
@@ -89,8 +90,6 @@ class TrustAndRestrict extends React.Component {
* if it has been alreday added to the opposite list. Displays appropriate warnings.
*/
addSite() {
// from node-validator
const isValidUrlRegex = /^(?!mailto:)(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))|localhost)(?::\d{2,5})?(?:\/[^\s]*)?$/i;
let pageHost;
let list;
let listType;
@@ -121,10 +120,12 @@ class TrustAndRestrict extends React.Component {
pageHost = pageHost.toLowerCase().replace(/^(http[s]?:\/\/)?(www\.)?/, '');

// Check for Validity
if (pageHost.length >= 2083 || !isValidUrlRegex.test(pageHost)) {
if (pageHost.length >= 2083
|| !this.isValidUrlWildcardOrRegex(pageHost)) {
this.showWarning(t('white_black_list_error_invalid_url'));
return;
}

// Check for Duplicates
if (list.includes(pageHost)) {
this.showWarning(duplicateWarning);
@@ -139,6 +140,40 @@ class TrustAndRestrict extends React.Component {
this.props.actions.updateSitePolicy({ type: listType, pageHost });
}
This conversation was marked as resolved by christophertino

This comment has been minimized.

@christophertino

christophertino Feb 26, 2020
Member

Once the site is successfully added to the list, clear the input field

This comment has been minimized.

@benstrumeyer

benstrumeyer Feb 27, 2020
Author Contributor

Input field cleared!


isValidUrlWildcardOrRegex(pageHost) {
// Check for valid URL
// from node-validator
const isValidUrlRegex = /^(?!mailto:)(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?:(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))|localhost)(?::\d{2,5})?(?:\/[^\s]*)?$/i;
if (isValidUrlRegex.test(pageHost)) return true;

// Check for valid wildcard
const escapedPattern = pageHost.replace(/[|\\{}()[\]^$+*?.-]/g, '\\$&');
const wildcardPattern = escapedPattern.replace(/\*/g, '.*');
let isValidWildcard = true;
try {
// eslint-disable-next-line
new RegExp(wildcardPattern);
} catch {
isValidWildcard = false;
}

if (isValidWildcard) return true;

// Prevent ReDoS attack
if (!safe(pageHost)) return false;

// Check for valid regex
let isValidRegex = true;
try {
// eslint-disable-next-line
new RegExp(pageHost);
} catch {
isValidRegex = false;
}

return isValidRegex;
}

/**
* Save current warning in state.
* @param {string} warning warning to save
@@ -0,0 +1,91 @@
/**
* Rewards Test Component
*
* Ghostery Browser Extension
* https://www.ghostery.com/
*
* Copyright 2019 Ghostery, Inc. All rights reserved.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0
*/

import React from 'react';
import renderer from 'react-test-renderer';
import { shallow } from 'enzyme';
import { when } from 'jest-when';
import TrustAndRestrict from '../TrustAndRestrict';

describe('app/panel/components/Settings/TrustAndRestrict', () => {
describe('Snapshot test with react-test-renderer', () => {
test('Testing TrustAndRestrict is rendering', () => {
const wrapper = renderer.create(
<TrustAndRestrict />
).toJSON();
expect(wrapper).toMatchSnapshot();
});
});
});

describe('app/panel/components/Settings/', () => {
test('isValidUrlWildcardOrRegex should return true with url entered', () => {
const wrapper = shallow(<TrustAndRestrict />);
const input = 'ghostery.com';

const fn = jest.spyOn(wrapper.instance(), 'isValidUrlWildcardOrRegex');
when(fn)
.calledWith(input)
.mockReturnValue(true);
const returnValue = wrapper.instance().isValidUrlWildcardOrRegex(input);
expect(returnValue).toBe(true);
});

test('isValidUrlWildcardOrRegex should return true with wildcard URL entered', () => {
const wrapper = shallow(<TrustAndRestrict />);
const input = 'developer.*.org';

const fn = jest.spyOn(wrapper.instance(), 'isValidUrlWildcardOrRegex');
when(fn)
.calledWith(input)
.mockReturnValue(true);
const returnValue = wrapper.instance().isValidUrlWildcardOrRegex(input);
expect(returnValue).toBe(true);
});

test('isValidUrlWildcardOrRegex should return true with regex URL entered', () => {
const wrapper = shallow(<TrustAndRestrict />);
const input = '[ds]eveloper.mozilla.org';

const fn = jest.spyOn(wrapper.instance(), 'isValidUrlWildcardOrRegex');
when(fn)
.calledWith(input)
.mockReturnValue(true);
const returnValue = wrapper.instance().isValidUrlWildcardOrRegex(input);
expect(returnValue).toBe(true);
});

test('isValidUrlWildcardOrRegex should return false with unsafe regex entered', () => {
const wrapper = shallow(<TrustAndRestrict />);
const input = '/^(\w+\s?)*$/';

const fn = jest.spyOn(wrapper.instance(), 'isValidUrlWildcardOrRegex');
when(fn)
.calledWith(input)
.mockReturnValue(false);
const returnValue = wrapper.instance().isValidUrlWildcardOrRegex(input);
expect(returnValue).toBe(false);
});

test('isValidUrlWildcardOrRegex should return false with incorrect regex format entered', () => {
const wrapper = shallow(<TrustAndRestrict />);
const input = '[.ghostery.com';

const fn = jest.spyOn(wrapper.instance(), 'isValidUrlWildcardOrRegex');
when(fn)
.calledWith(input)
.mockReturnValue(false);
const returnValue = wrapper.instance().isValidUrlWildcardOrRegex(input);
expect(returnValue).toBe(false);
});
});
@@ -0,0 +1,119 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`app/panel/components/Settings/TrustAndRestrict Snapshot test with react-test-renderer Testing TrustAndRestrict is rendering 1`] = `
<div
className="s-trust-restrict-panel s-tabs-panel"
>
<div
className="row"
>
<div
className="columns"
>
<h3>
settings_trusted_restricted_sites
</h3>
</div>
</div>
<div
className="s-trust-restrict-menu"
>
<div
className="s-active-pane s-pane-title"
id="showTrustedSites"
onClick={[Function]}
>
<span>
settings_trusted_sites
</span>
</div>
<div
className="s-pane-title-next"
id="showRestrictedSites"
onClick={[Function]}
>
<span>
settings_restricted_sites
</span>
</div>
</div>
<div
className="s-sites-pane"
>
<div
className="row"
>
<div
className="columns"
>
<div
className="s-sites-input-box"
>
<input
onChange={[Function]}
onKeyDown={[Function]}
placeholder="settings_sites_placeholder"
type="text"
value=""
/>
<div
className="s-sites-input-icon"
onClick={[Function]}
/>
</div>
<div
className="s-site-description"
>
<span>
settings_trusted_sites_description
</span>
</div>
<div
className="s-invisible s-callout"
>

</div>
</div>
</div>
</div>
<div
className="s-hide s-sites-pane"
>
<div
className="row"
>
<div
className="columns"
>
<div
className="s-sites-input-box"
>
<input
onChange={[Function]}
onKeyDown={[Function]}
placeholder="settings_sites_placeholder"
type="text"
value=""
/>
<div
className="s-sites-input-icon"
onClick={[Function]}
/>
</div>
<div
className="s-site-description"
>
<span>
settings_restricted_sites_description
</span>
</div>
<div
className="s-invisible s-callout"
>

</div>
</div>
</div>
</div>
</div>
`;
@@ -64,6 +64,7 @@
"redux-object": "^0.5.10",
"redux-thunk": "^2.2.0",
"rsvp": "^4.8.5",
"safe-regex": "^2.1.1",
"spanan": "^2.0.0",
"ua-parser-js": "^0.7.21",
"underscore": "^1.9.2",
@@ -91,6 +92,7 @@
"eslint-plugin-react": "^7.18.3",
"fs-extra": "^8.1.0",
"jest": "^25.1.0",
"jest-when": "^2.7.0",
"jsdoc": "^3.6.3",
"jsonfile": "^5.0.0",
"license-checker": "^25.0.1",
@@ -14,7 +14,6 @@
*/

/* eslint no-param-reassign: 0 */

import c2pDb from './Click2PlayDb';
import conf from './Conf';
import globals from './Globals';
@@ -71,7 +70,8 @@ class Policy {
// TODO: speed up
for (let i = 0; i < num_sites; i++) {
// TODO match from the beginning of the string to avoid false matches (somewhere in the querystring for instance)
if (replacedUrl === sites[i]) {
if (replacedUrl === sites[i]
|| this.matchesWildcardOrRegex(replacedUrl, sites[i])) {
return sites[i];
}
}
@@ -119,7 +119,8 @@ class Policy {
// TODO: speed up
for (let i = 0; i < num_sites; i++) {
// TODO match from the beginning of the string to avoid false matches (somewhere in the querystring for instance)
if (replacedUrl === sites[i]) {
if (replacedUrl === sites[i]
|| this.matchesWildcardOrRegex(replacedUrl, sites[i])) {
return sites[i];
}
}
@@ -174,6 +175,32 @@ class Policy {
}
return { block: false, reason: allowedOnce ? BLOCK_REASON_C2P_ALLOWED_ONCE : BLOCK_REASON_GLOBAL_UNBLOCKED };
}

/**
* Check given url against pattern which might be a wildcard, or a regex
* @param {string} url site url
* @param {string} pattern regex pattern
* @return {boolean}
*/
matchesWildcardOrRegex(url, pattern) {
// Input string might be a wildcard
const escapedPattern = pattern.replace(/[|\\{}()[\]^$+*?.-]/g, '\\$&');
const wildcardPattern = escapedPattern.replace(/\*/g, '.*');
const wildcardRegex = RegExp(wildcardPattern);
console.log('url: ', url);
console.log('wildcardPattern: ', wildcardPattern);

if (wildcardRegex.test(url)) { return true; }

console.log('pattern: ', pattern);
// or a regex
const regex = RegExp(pattern);
if (regex.test(url)) { return true; }

console.log('returning false');

return false;
}
}

export default Policy;
ProTip! Use n and p to navigate between commits in a pull request.