From 3f38bdfe7bc17e019bf048c79c9e8f1336b6f3d3 Mon Sep 17 00:00:00 2001 From: ArtemBaskal Date: Thu, 21 May 2020 20:17:27 +0300 Subject: [PATCH] - client: Match client IP strictly --- client/src/components/Dashboard/Clients.js | 14 ++- .../components/Settings/Dns/Access/Form.js | 2 + client/src/helpers/constants.js | 5 ++ client/src/helpers/helpers.js | 87 ++++++++++++++++--- 4 files changed, 92 insertions(+), 16 deletions(-) diff --git a/client/src/components/Dashboard/Clients.js b/client/src/components/Dashboard/Clients.js index 7fcaf220ab0..660d3140472 100644 --- a/client/src/components/Dashboard/Clients.js +++ b/client/src/components/Dashboard/Clients.js @@ -6,8 +6,8 @@ import { Trans, withTranslation } from 'react-i18next'; import Card from '../ui/Card'; import Cell from '../ui/Cell'; -import { getPercent } from '../../helpers/helpers'; -import { STATUS_COLORS } from '../../helpers/constants'; +import { getPercent, isClientInIpsOrCidrs } from '../../helpers/helpers'; +import { BLOCKED_CLIENT, STATUS_COLORS } from '../../helpers/constants'; import { formatClientCell } from '../../helpers/formatClientCell'; const getClientsPercentColor = (percent) => { @@ -56,7 +56,12 @@ const renderBlockingButton = (blocked, ip, handleClick, processing) => { ); }; -const isBlockedClient = (clients, ip) => !!(clients && clients.includes(ip)); +const isBlockedClient = (rawClients, client) => { + if (!rawClients || !client) { + return false; + } + return isClientInIpsOrCidrs(rawClients, client); +}; const clientCell = (t, toggleClientStatus, processing, disallowedClients) => function cell(row) { const { value } = row; @@ -67,7 +72,8 @@ const clientCell = (t, toggleClientStatus, processing, disallowedClients) => fun
{formatClientCell(row, t)}
- {renderBlockingButton(blocked, value, toggleClientStatus, processing)} + {blocked !== BLOCKED_CLIENT.CIDR + && renderBlockingButton(blocked, value, toggleClientStatus, processing)} ); }; diff --git a/client/src/components/Settings/Dns/Access/Form.js b/client/src/components/Settings/Dns/Access/Form.js index 62e587c5a3a..7cdec77a272 100644 --- a/client/src/components/Settings/Dns/Access/Form.js +++ b/client/src/components/Settings/Dns/Access/Form.js @@ -4,6 +4,7 @@ import { Field, reduxForm } from 'redux-form'; import { Trans, withTranslation } from 'react-i18next'; import flow from 'lodash/flow'; import { renderTextareaField } from '../../../../helpers/form'; +import { normalizeMultiline } from '../../../../helpers/helpers'; const fields = [ { @@ -44,6 +45,7 @@ const Form = (props) => { type="text" className="form-control form-control--textarea font-monospace" disabled={disabled} + normalizeOnBlur={normalizeMultiline} /> ; diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index 3f21c8375a9..84f6898a38c 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -379,3 +379,8 @@ export const DNS_REQUEST_OPTIONS = { PARALLEL_REQUESTS: 'parallel_requests', FASTEST_ADDR: 'fastest_addr', }; + +export const BLOCKED_CLIENT = { + IP: 'IP', + CIDR: 'CIDR', +}; diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js index 0110a95bac7..3e096017af7 100644 --- a/client/src/helpers/helpers.js +++ b/client/src/helpers/helpers.js @@ -1,3 +1,4 @@ +/* eslint-disable no-bitwise */ import 'url-polyfill'; import dateParse from 'date-fns/parse'; import dateFormat from 'date-fns/format'; @@ -24,6 +25,7 @@ import { DEFAULT_LANGUAGE, FILTERED_STATUS, FILTERED, + BLOCKED_CLIENT, } from './constants'; /** @@ -167,7 +169,10 @@ export const getPercent = (amount, number) => { return 0; }; -export const captitalizeWords = (text) => text.split(/[ -_]/g).map((str) => str.charAt(0).toUpperCase() + str.substr(1)).join(' '); +export const captitalizeWords = (text) => text.split(/[ -_]/g) + .map((str) => str.charAt(0) + .toUpperCase() + str.substr(1)) + .join(' '); export const getInterfaceIp = (option) => { const onlyIPv6 = option.ip_addresses.every((ip) => ip.includes(':')); @@ -187,9 +192,10 @@ export const getInterfaceIp = (option) => { export const getIpList = (interfaces) => { let list = []; - Object.keys(interfaces).forEach((item) => { - list = [...list, ...interfaces[item].ip_addresses]; - }); + Object.keys(interfaces) + .forEach((item) => { + list = [...list, ...interfaces[item].ip_addresses]; + }); return list.sort(); }; @@ -283,7 +289,9 @@ export const normalizeTextarea = (text) => { return []; } - return text.replace(/[;, ]/g, '\n').split('\n').filter((n) => n); + return text.replace(/[;, ]/g, '\n') + .split('\n') + .filter((n) => n); }; /** @@ -307,7 +315,10 @@ export const normalizeTopClients = (topClients) => topClients.reduce( // eslint-disable-next-line no-param-reassign nameToCountMap.configured[infoName] = count; return nameToCountMap; - }, { auto: {}, configured: {} }, + }, { + auto: {}, + configured: {}, + }, ); export const getClientInfo = (clients, ip) => { @@ -321,7 +332,10 @@ export const getClientInfo = (clients, ip) => { const { name, whois_info } = client; const whois = Object.keys(whois_info).length > 0 ? whois_info : ''; - return { name, whois }; + return { + name, + whois, + }; }; export const getAutoClientInfo = (clients, ip) => { @@ -334,7 +348,10 @@ export const getAutoClientInfo = (clients, ip) => { const { name, whois_info } = client; const whois = Object.keys(whois_info).length > 0 ? whois_info : ''; - return { name, whois }; + return { + name, + whois, + }; }; export const sortClients = (clients) => { @@ -344,7 +361,8 @@ export const sortClients = (clients) => { if (nameA > nameB) { return 1; - } if (nameA < nameB) { + } + if (nameA < nameB) { return -1; } @@ -366,7 +384,8 @@ export const secondsToMilliseconds = (seconds) => { return seconds; }; -export const normalizeRulesTextarea = (text) => text && text.replace(/^\n/g, '').replace(/\n\s*\n/g, '\n'); +export const normalizeRulesTextarea = (text) => text && text.replace(/^\n/g, '') + .replace(/\n\s*\n/g, '\n'); export const isVersionGreater = (currentVersion, previousVersion) => ( versionCompare(currentVersion, previousVersion) === -1 @@ -449,10 +468,17 @@ export const getCurrentFilter = (url, filters) => { if (filter) { const { enabled, name, url } = filter; - return { enabled, name, url }; + return { + enabled, + name, + url, + }; } - return { name: '', url: '' }; + return { + name: '', + url: '', + }; }; /** @@ -463,3 +489,40 @@ export const formatNumber = (num) => { const currentLanguage = i18n.languages[0] || DEFAULT_LANGUAGE; return num.toLocaleString(currentLanguage); }; + +export const normalizeMultiline = (multiline) => `${normalizeTextarea(multiline) + .map((line) => line.trim()) + .join('\n')}\n`; +/** + * @param ip {string} + * @returns {number} + */ +export const ipToInt = (ip) => ip.split('.') + .reduce((int, oct) => (int << 8) + parseInt(oct, 10), 0) >>> 0; +/** + * @param cidr {string} + * @param ip {string} + * @returns {boolean} + */ +export const isIpInCidr = (cidr, ip) => { + const [range, bits = 32] = cidr.split('/'); + const mask = ~((2 ** (32 - bits)) - 1); + return (ipToInt(ip) & mask) === (ipToInt(range) & mask); +}; +/** + * @param rawClients {string} + * @param currentClient {string} + * @returns {boolean | 'CIDR' | 'IP'} + */ +export const isClientInIpsOrCidrs = (rawClients, currentClient) => rawClients.split('\n') + .reduce((acc, curr) => { + if (acc) { + return acc; + } + if (curr.includes('/') && isIpInCidr(curr, currentClient)) { + return BLOCKED_CLIENT.CIDR; + } if (curr === currentClient) { + return BLOCKED_CLIENT.IP; + } + return false; + }, false);