Merge pull request #688 from ONLYOFFICE/feature/ip-security

Feature/ip security
This commit is contained in:
Viktor Fomin 2022-06-07 13:43:35 +03:00 committed by GitHub
commit 4913ba7bf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 363 additions and 19 deletions

View File

@ -55,18 +55,32 @@ export function setDNSSettings(dnsName, enable) {
});
}
export function getIpRestrictions() {
return request({
method: "get",
url: "/settings/iprestrictions",
});
}
export function setIpRestrictions(data) {
return request({
method: "put",
url: "/settings/iprestrictions.json",
url: "/settings/iprestrictions",
data,
});
}
export function getIpRestrictionsEnable() {
return request({
method: "get",
url: "/settings/iprestrictions/settings",
});
}
export function setIpRestrictionsEnable(data) {
return request({
method: "put",
url: "/settings/iprestrictions/settings.json",
url: "/settings/iprestrictions/settings",
data,
});
}

View File

@ -30,6 +30,8 @@ class SettingsStore {
theme = Base;
trustedDomains = [];
trustedDomainsType = 0;
ipRestrictionEnable = false;
ipRestrictions = [];
timezone = "UTC";
timezones = [];
tenantAlias = "";
@ -475,6 +477,33 @@ class SettingsStore {
this.tenantAlias = tenantAlias;
};
getIpRestrictions = async () => {
const res = await api.settings.getIpRestrictions();
this.ipRestrictions = res?.map((el) => el.ip);
console.log(this.ipRestrictions);
};
setIpRestrictions = async (ips) => {
const data = {
ips: ips,
};
const res = await api.settings.setIpRestrictions(data);
this.ipRestrictions = res;
};
getIpRestrictionsEnable = async () => {
const res = await api.settings.getIpRestrictionsEnable();
this.ipRestrictionEnable = res.enable;
};
setIpRestrictionsEnable = async (enable) => {
const data = {
enable: enable,
};
const res = await api.settings.setIpRestrictionsEnable(data);
this.ipRestrictionEnable = res.enable;
};
setIsBurgerLoading = (isBurgerLoading) => {
this.isBurgerLoading = isBurgerLoading;
};

View File

@ -8,6 +8,7 @@
"AccessRightsUsersFromList": "{{users}} from the list",
"AccessSettings": "Access settings",
"AddAdmins": "Add admins",
"AddAllowedIP": "Add allowed IP address",
"AddName": "Add Name",
"AddTrustedDomain": "Add trusted domain",
"AdminInModules": "Admin in modules",
@ -67,6 +68,10 @@
"GroupLead": "Group Lead",
"Groups": "Groups",
"Guests": "Guests",
"IPSecurity": "IP Security",
"IPSecurityDescription": "IP Security is used to restrict login to the portal from all IP addresses except certain addresses.",
"IPSecurityHelper": "You can set the allowed IP addresses using either exact IP addresses in the IPv4 format (#.#.#.#, where # is a numeric value from 0 to 255) or IP range (in the #.#.#.#-#.#.#.# format).",
"IPSecurityWarningHelper": "First you need to specify your current IP or the IP range your current IP address belongs to, otherwise your portal access will be blocked right after you save the settings. The portal owner will have the portal access from any IP address.",
"Job/Title": "Job/Title",
"LanguageAndTimeZoneSettingsDescription": "Language and Time Zone Settings is a way to change the language of the whole portal for all portal users and to configure the time zone so that all the events of the portal will be shown with the correct date and time.",
"LanguageTimeSettingsTooltip": "<0>{{text}}</0> is a way to change the language of the whole portal for all portal users and to configure the time zone so that all the events of the ONLYOFFICE portal will be shown with the correct date and time.",

View File

@ -8,6 +8,7 @@
"AccessRightsUsersFromList": "Участников со статусом {{users}} из списка",
"AccessSettings": "Настройки доступа",
"AddAdmins": "Добавить администраторов",
"AddAllowedIP": "Добавить разрешенный IP-адрес",
"AddName": "Добавьте наименование",
"AddTrustedDomain": "Добавить доверенный домен",
"AdminInModules": "Администратор в модулях",
@ -67,6 +68,10 @@
"GroupLead": "Руководитель группы",
"Groups": "Группы",
"Guests": "Гости",
"IPSecurity": "IP-безопасность",
"IPSecurityDescription": "Настройки IP-безопасности используются для ограничения возможности входа на портал со всех IP-адресов, кроме указанных.",
"IPSecurityHelper": "Вы можете задать разрешенные IP-адреса, указав конкретные значения IP-адресов в формате IPv4 (#.#.#.#, где # - это число от 0 до 255) или диапазон IP-адресов (в формате #.#.#.#-#.#.#.#).",
"IPSecurityWarningHelper": "Первым необходимо указать ваш текущий IP-адрес или диапазон IP-адресов, в который входит ваш текущий IP, иначе после сохранения настроек Вам будет заблокирован доступ к порталу. Владелец портала будет иметь доступ с любого IP-адреса.",
"Job/Title": "Должность/Позиция",
"LanguageAndTimeZoneSettingsDescription": "Настройки языка и часового пояса позволяют изменить язык всего портала для всех пользователей и настроить часовой пояс, чтобы все события на портале отображались с корректной датой и временем.",
"LanguageTimeSettingsTooltip": "<0>{{text}}</0> позволяют изменить язык всего портала для всех пользователей и настроить часовой пояс, чтобы все события на портале ONLYOFFICE отображались с корректной датой и временем.",

View File

@ -7,6 +7,7 @@ import { MainContainer } from "../StyledSecurity";
import TfaSection from "./tfa";
import PasswordStrengthSection from "./passwordStrength";
import TrustedMailSection from "./trustedMail";
import IpSecuritySection from "./ipSecurity";
import MobileView from "./mobileView";
import CategoryWrapper from "../sub-components/category-wrapper";
import { size } from "@appserver/components/utils/device";
@ -58,6 +59,14 @@ const AccessPortal = (props) => {
tooltipUrl={`https://helpcenter.onlyoffice.com/${lng}/administration/configuration.aspx#ChangingSecuritySettings_block`}
/>
<TrustedMailSection />
<hr />
<CategoryWrapper
t={t}
title={t("IPSecurity")}
tooltipContent={t("IPSecurityDescription")}
tooltipTitle={t("IPSecurityDescription")}
/>
<IpSecuritySection />
</MainContainer>
);
};

View File

@ -0,0 +1,254 @@
import React, { useState, useEffect } from "react";
import styled from "styled-components";
import { withRouter } from "react-router";
import { withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import Text from "@appserver/components/text";
import RadioButtonGroup from "@appserver/components/radio-button-group";
import toastr from "@appserver/components/toast/toastr";
import { LearnMoreWrapper } from "../StyledSecurity";
import UserFields from "../sub-components/user-fields";
import { size } from "@appserver/components/utils/device";
import { saveToSessionStorage, getFromSessionStorage } from "../../../utils";
import isEqual from "lodash/isEqual";
import SaveCancelButtons from "@appserver/components/save-cancel-buttons";
const MainContainer = styled.div`
width: 100%;
.page-subtitle {
margin-bottom: 10px;
}
.user-fields {
margin-bottom: 18px;
}
.box {
margin-bottom: 11px;
}
.warning-text {
margin-bottom: 9px;
}
.save-cancel-buttons {
margin-top: 24px;
}
`;
const IpSecurity = (props) => {
const {
t,
history,
ipRestrictionEnable,
setIpRestrictionsEnable,
ipRestrictions,
setIpRestrictions,
initSettings,
isInit,
} = props;
const regexp = /^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|$)){4}$/; //check ip valid
const [enable, setEnable] = useState(false);
const [ips, setIps] = useState();
const [showReminder, setShowReminder] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const getSettings = () => {
const currentSettings = getFromSessionStorage("currentIPSettings");
const defaultData = {
enable: ipRestrictionEnable,
ips: ipRestrictions,
};
saveToSessionStorage("defaultIPSettings", defaultData);
if (currentSettings) {
setEnable(currentSettings.enable);
setIps(currentSettings.ips);
} else {
setEnable(ipRestrictionEnable);
setIps(ipRestrictions);
}
};
useEffect(() => {
checkWidth();
window.addEventListener("resize", checkWidth);
if (!isInit) initSettings().then(() => setIsLoading(true));
else setIsLoading(true);
return () => window.removeEventListener("resize", checkWidth);
}, []);
useEffect(() => {
if (!isInit) return;
getSettings();
}, [isLoading]);
useEffect(() => {
if (!isLoading) return;
const defaultSettings = getFromSessionStorage("defaultIPSettings");
const newSettings = {
enable: enable,
ips: ips,
};
saveToSessionStorage("currentIPSettings", newSettings);
if (isEqual(defaultSettings, newSettings)) {
setShowReminder(false);
} else {
setShowReminder(true);
}
}, [enable, ips]);
const checkWidth = () => {
window.innerWidth > size.smallTablet &&
history.location.pathname.includes("ip") &&
history.push("/settings/security/access-portal");
};
const onSelectType = (e) => {
setEnable(e.target.value === "enable" ? true : false);
};
const onChangeInput = (e, index) => {
let newInputs = Array.from(ips);
newInputs[index] = e.target.value;
setIps(newInputs);
};
const onDeleteInput = (index) => {
let newInputs = Array.from(ips);
newInputs.splice(index, 1);
setIps(newInputs);
};
const onClickAdd = () => {
setIps([...ips, ""]);
};
const onSaveClick = async () => {
setIsSaving(true);
const valid = ips.map((ip) => regexp.test(ip));
if (valid.includes(false)) {
setIsSaving(false);
return;
}
try {
await setIpRestrictions(ips);
await setIpRestrictionsEnable(enable);
saveToSessionStorage("defaultIPSettings", {
enable: enable,
ips: ips,
});
setShowReminder(false);
toastr.success(t("SuccessfullySaveSettingsMessage"));
} catch (error) {
toastr.error(error);
}
setIsSaving(false);
};
const onCancelClick = () => {
const defaultSettings = getFromSessionStorage("defaultIPSettings");
setEnable(defaultSettings.enable);
setIps(defaultSettings.ips);
setShowReminder(false);
};
return (
<MainContainer>
<LearnMoreWrapper>
<Text className="page-subtitle">{t("IPSecurityHelper")}</Text>
</LearnMoreWrapper>
<RadioButtonGroup
className="box"
fontSize="13px"
fontWeight="400"
name="group"
orientation="vertical"
spacing="8px"
options={[
{
label: t("Disabled"),
value: "disabled",
},
{
label: t("Common:Enable"),
value: "enable",
},
]}
selected={enable ? "enable" : "disabled"}
onClick={onSelectType}
/>
{enable && (
<UserFields
className="user-fields"
inputs={ips}
buttonLabel={t("AddAllowedIP")}
onChangeInput={onChangeInput}
onDeleteInput={onDeleteInput}
onClickAdd={onClickAdd}
regexp={regexp}
/>
)}
{enable && (
<>
<Text
color="#F21C0E"
fontSize="16px"
fontWeight="700"
className="warning-text"
>
{t("Common:Warning")}!
</Text>
<Text>{t("IPSecurityWarningHelper")}</Text>
</>
)}
<SaveCancelButtons
className="save-cancel-buttons"
onSaveClick={onSaveClick}
onCancelClick={onCancelClick}
showReminder={showReminder}
reminderTest={t("YouHaveUnsavedChanges")}
saveButtonLabel={t("Common:SaveButton")}
cancelButtonLabel={t("Common:CancelButton")}
displaySettings={true}
hasScroll={false}
isSaving={isSaving}
/>
</MainContainer>
);
};
export default inject(({ auth, setup }) => {
const {
ipRestrictionEnable,
setIpRestrictionsEnable,
ipRestrictions,
setIpRestrictions,
} = auth.settingsStore;
const { initSettings, isInit } = setup;
return {
ipRestrictionEnable,
setIpRestrictionsEnable,
ipRestrictions,
setIpRestrictions,
initSettings,
isInit,
};
})(withTranslation(["Settings", "Common"])(withRouter(observer(IpSecurity))));

View File

@ -37,6 +37,12 @@ const MobileView = (props) => {
url="/settings/security/access-portal/trusted-mail"
onClickLink={onClickLink}
/>
<MobileCategoryWrapper
title={t("IPSecurity")}
subtitle={t("IPSecurityDescription")}
url="/settings/security/access-portal/ip"
onClickLink={onClickLink}
/>
</MainContainer>
);
};

View File

@ -10,10 +10,12 @@ const CategoryWrapper = (props) => {
const tooltip = () => (
<StyledTooltip>
<Text className="subtitle">{tooltipTitle}</Text>
<Link target="_blank" isHovered href={tooltipUrl}>
{t("Common:LearnMore")}
</Link>
<Text className={tooltipUrl ? "subtitle" : ""}>{tooltipTitle}</Text>
{tooltipUrl && (
<Link target="_blank" isHovered href={tooltipUrl}>
{t("Common:LearnMore")}
</Link>
)}
</StyledTooltip>
);

View File

@ -92,12 +92,24 @@ const UserFields = (props) => {
<div className={className}>
{inputs ? (
inputs.map((input, index) => {
const error = !regexp.test(input);
let newInput1;
let newInput2;
if (input.includes("-")) {
newInput1 = input.split("-")[0];
newInput2 = input.split("-")[1];
}
const error = newInput2
? input.split("-").length - 1 > 1 ||
!regexp.test(newInput1) ||
!regexp.test(newInput2)
: !regexp.test(input);
return (
<StyledInputWrapper key={`domain-input-${index}`}>
<StyledInputWrapper key={`user-input-${index}`}>
<TextInput
id={`domain-input-${index}`}
id={`user-input-${index}`}
isAutoFocussed={true}
value={input}
onChange={(e) => onChangeInput(e, index)}

View File

@ -14,6 +14,9 @@ const PasswordStrengthPage = lazy(() =>
const TrustedMailPage = lazy(() =>
import("./categories/security/access-portal/trustedMail")
);
const IpSecurityPage = lazy(() =>
import("./categories/security/access-portal/ipSecurity")
);
const CommonSettings = lazy(() => import("./categories/common/index.js"));
@ -92,6 +95,10 @@ const TRUSTED_MAIL_PAGE_URL = combineUrl(
PROXY_BASE_URL,
"/security/access-portal/trusted-mail"
);
const IP_SECURITY_PAGE_URL = combineUrl(
PROXY_BASE_URL,
"/security/access-portal/ip"
);
const ADMINS_URL = combineUrl(PROXY_BASE_URL, "/security/access-rights/admins");
const THIRD_PARTY_URL = combineUrl(
@ -146,6 +153,7 @@ const Settings = (props) => {
path={TRUSTED_MAIL_PAGE_URL}
component={TrustedMailPage}
/>
<Route exact path={IP_SECURITY_PAGE_URL} component={IpSecurityPage} />
<Route exact path={THIRD_PARTY_URL} component={ThirdPartyServices} />
<Route

View File

@ -77,6 +77,12 @@ export const settingsTree = [
link: "trusted-mail",
tKey: "TrustedMail",
},
{
key: "1-0-3",
icon: "",
link: "ip",
tKey: "IPSecurity",
},
],
},
{

View File

@ -53,6 +53,8 @@ class SettingsSetupStore {
if (authStore.isAuthenticated) {
await authStore.settingsStore.getPortalPasswordSettings();
await authStore.tfaStore.getTfaType();
await authStore.settingsStore.getIpRestrictionsEnable();
await authStore.settingsStore.getIpRestrictions();
}
};
@ -216,14 +218,6 @@ class SettingsSetupStore {
const res = await api.settings.setMailDomainSettings(dnsName, enable);
};
setIpRestrictions = async (data) => {
const res = await api.settings.setIpRestrictions(data);
};
setIpRestrictionsEnable = async (data) => {
const res = await api.settings.setIpRestrictionsEnable(data);
};
setMessageSettings = async (turnOn) => {
const res = await api.settings.setMessageSettings(turnOn);
};

View File

@ -1052,7 +1052,7 @@ Scenario("Trusted mail settings change test success", async ({ I }) => {
I.click("Add trusted domain");
I.seeElement("#domain-input-0");
I.fillField("#domain-input-0", "test.com");
I.fillField("#user-input-0", "test.com");
I.click("Save");
@ -1089,7 +1089,7 @@ Scenario("Trusted mail settings change test error", async ({ I }) => {
I.click("Add trusted domain");
I.seeElement("#domain-input-0");
I.fillField("#domain-input-0", "test");
I.fillField("#user-input-0", "test");
I.click("Save");