Merge branch 'feature/sso' of https://github.com/ONLYOFFICE/AppServer into feature/sso

This commit is contained in:
Nikolay Rechkin 2022-08-05 17:32:20 +03:00
commit 8d7de4ee8c
23 changed files with 186 additions and 108 deletions

View File

@ -10,7 +10,7 @@
"CustomEntryTooltip": "The caption for the button used to login to the portal with Single Sign-on service",
"DownloadMetadataXML": "Download SP metadata XML",
"EmptyFieldErrorMessage": "The field is empty",
"Encryption": "encryption",
"Encryption": "Encryption",
"FirstName": "First Name",
"ForbiddenPageDescription": "To make changes to this section, go to the desktop version.",
"ForbiddenPageHeader": "Section unavailable.",
@ -42,8 +42,8 @@
"ShowAdditionalParameters": "Show advanced settings",
"SignOnEndpointUrl": "IdP Single Sign-On Endpoint URL",
"SignOnEndpointUrlTooltip": "The URL used for the single sign-on on the Identity provider side",
"Signing": "signing",
"SigningAndEncryption": "signing and encryption",
"Signing": "Signing",
"SigningAndEncryption": "Signing and encryption",
"SsoIntro": "The Single Sign-on section allows you to enable/disable third-party authentication using SAML, thereby providing a more quick, easy and secure way to access the portal for users.",
"StandardDecryptionAlgorithm": "Default Decrypt Algorithm",
"Title": "Title",

View File

@ -10,7 +10,7 @@
"CustomEntryTooltip": "Надпись для кнопки, которая используется для входа на портал с помощью сервиса Single Sign-on",
"DownloadMetadataXML": "Скачать XML-файл метаданных поставщика сервиса",
"EmptyFieldErrorMessage": "Поле не заполнено",
"Encryption": "шифрование",
"Encryption": "Шифрование",
"FirstName": "Имя",
"ForbiddenPageDescription": "Для внесения изменений в данный раздел, перейдите в десктопную версию.",
"ForbiddenPageHeader": "Раздел недоступен.",
@ -41,8 +41,8 @@
"ShowAdditionalParameters": "Показать дополнительные параметры",
"SignOnEndpointUrl": "URL-адрес конечной точки единого входа IdP:",
"SignOnEndpointUrlTooltip": "URL-адрес, используемый для единого входа на стороне поставщика учетных записей",
"Signing": "подпись",
"SigningAndEncryption": "подпись и шифрование",
"Signing": "Подпись",
"SigningAndEncryption": "Подпись и шифрование",
"SsoIntro": "Раздел « Единый вход » позволяет включать / отключать стороннюю аутентификацию с использованием SAML, тем самым обеспечивая более быстрый, простой и безопасный способ доступа к порталу для пользователей.",
"StandardDecryptionAlgorithm": "Стандартный алгоритм расшифровки:",
"Title": "Должность",

View File

@ -28,7 +28,7 @@ const HeaderContainer = styled.div`
max-width: calc(100vw - 32px);
h1 {
line-height: 53px;
line-height: 69px;
}
.action-wrapper {

View File

@ -74,8 +74,15 @@ const IdpSettings = (props) => {
: ssoUrlRedirectHasError
}
>
<Box displayProp="flex" flexDirection="row" marginProp="8px 0 4px 0">
<Text noSelect>{t("Binding")}</Text>
<Box
displayProp="flex"
alignItems="center"
flexDirection="row"
marginProp="5px 0"
>
<Text fontSize="12px" fontWeight={400} noSelect>
{t("Binding")}
</Text>
<RadioButtonGroup
className="radio-button-group"
@ -84,7 +91,7 @@ const IdpSettings = (props) => {
onClick={setInput}
options={bindingOptions}
selected={ssoBinding}
spacing="21px"
spacing="20px"
tabIndex={6}
/>
</Box>
@ -107,8 +114,15 @@ const IdpSettings = (props) => {
: sloUrlRedirectHasError
}
>
<Box displayProp="flex" flexDirection="row" marginProp="8px 0 4px 0">
<Text>{t("Binding")}</Text>
<Box
displayProp="flex"
alignItems="center"
flexDirection="row"
marginProp="5px 0"
>
<Text fontSize="12px" fontWeight={400}>
{t("Binding")}
</Text>
<RadioButtonGroup
className="radio-button-group"
@ -117,7 +131,7 @@ const IdpSettings = (props) => {
onClick={setInput}
options={bindingOptions}
selected={sloBinding}
spacing="21px"
spacing="20px"
tabIndex={8}
/>
</Box>

View File

@ -3,8 +3,7 @@ import styled from "styled-components";
const StyledCertificatesTable = styled.div`
width: 100%;
max-width: 600px;
margin-top: -13px;
margin-bottom: 16px;
margin-bottom: 12px;
.header {
display: grid;
@ -27,9 +26,9 @@ const StyledCertificatesTable = styled.div`
.row {
width: 350px;
display: flex;
gap: 16px;
gap: 12px;
align-items: center;
padding: 12px 0;
padding: 11px 0 10px;
border-bottom: 1px solid #eceef1;
.column {

View File

@ -4,6 +4,7 @@ const StyledSsoPage = styled.div`
box-sizing: border-box;
outline: none;
max-width: 700px;
padding-top: 5px;
.intro-text {
margin-bottom: 18px;
@ -12,7 +13,13 @@ const StyledSsoPage = styled.div`
.toggle {
position: static;
margin-top: 2px;
margin-top: 1px;
}
.toggle-caption {
display: flex;
flex-direction: column;
gap: 4px;
}
.tooltip-button,
@ -21,11 +28,14 @@ const StyledSsoPage = styled.div`
}
.hide-button {
margin-left: 16px;
margin-left: 12px;
}
.hide-additional-button {
margin-left: 8px;
.field-input {
::placeholder {
font-size: 13px;
font-weight: 400;
}
}
.field-label-icon {
@ -60,7 +70,7 @@ const StyledSsoPage = styled.div`
}
.radio-button-group {
margin-left: 25px;
margin-left: 24px;
}
.combo-button-label {
@ -68,7 +78,7 @@ const StyledSsoPage = styled.div`
}
.checkbox-input {
margin: 6px 8px 6px 0;
margin: 10px 8px 6px 0;
}
.upload-button {
@ -76,10 +86,6 @@ const StyledSsoPage = styled.div`
width: 45px;
margin-left: 9px;
overflow: inherit;
& > div {
margin-top: 2px;
}
}
.save-button {
@ -123,6 +129,23 @@ const StyledSsoPage = styled.div`
font-weight: 600;
}
}
.metadata-field {
display: flex;
flex-direction: column;
gap: 4px;
margin-bottom: 16px;
max-width: 350px;
.input {
width: 350px;
}
p > div {
display: inline-flex;
margin-left: 4px;
}
}
`;
export default StyledSsoPage;

View File

@ -16,6 +16,7 @@ const AddIdpCertificateModal = (props) => {
idpIsModalVisible,
setInput,
idpCertificate,
isCertificateLoading,
} = props;
return (
@ -47,6 +48,8 @@ const AddIdpCertificateModal = (props) => {
label={t("Common:OKButton")}
onClick={addIdpCertificate}
primary
scale
isLoading={isCertificateLoading}
isDisabled={!idpCertificate}
size="normalTouchscreen"
/>
@ -54,6 +57,8 @@ const AddIdpCertificateModal = (props) => {
label={t("Common:CancelButton")}
onClick={closeIdpModal}
size="normalTouchscreen"
scale
isDisabled={isCertificateLoading}
/>
</ModalDialog.Footer>
</StyledModalDialog>
@ -67,6 +72,7 @@ export default inject(({ ssoStore }) => {
idpIsModalVisible,
setInput,
idpCertificate,
isCertificateLoading,
} = ssoStore;
return {
@ -75,5 +81,6 @@ export default inject(({ ssoStore }) => {
idpIsModalVisible,
setInput,
idpCertificate,
isCertificateLoading,
};
})(observer(AddIdpCertificateModal));

View File

@ -23,6 +23,7 @@ const AddSpCertificateModal = (props) => {
spCertificate,
spPrivateKey,
isGeneratedCertificate,
isCertificateLoading,
} = props;
const onGenerate = () => {
@ -55,6 +56,7 @@ const AddSpCertificateModal = (props) => {
onChange={setInput}
value={spCertificate}
isDisabled={isGeneratedCertificate}
placeholder={t("PlaceholderCert")}
/>
<Text isBold className="text-area-label" noSelect>
@ -82,13 +84,14 @@ const AddSpCertificateModal = (props) => {
onClick={addSpCertificate}
primary
size="normalTouchscreen"
isLoading={isCertificateLoading}
isDisabled={isGeneratedCertificate || !spCertificate || !spPrivateKey}
/>
<Button
label={t("Common:CancelButton")}
onClick={closeSpModal}
size="normalTouchscreen"
isDisabled={isGeneratedCertificate}
isDisabled={isGeneratedCertificate || isCertificateLoading}
/>
</ModalDialog.Footer>
</StyledModalDialog>
@ -105,6 +108,7 @@ export default inject(({ ssoStore }) => {
spCertificate,
spPrivateKey,
isGeneratedCertificate,
isCertificateLoading,
} = ssoStore;
return {
@ -116,5 +120,6 @@ export default inject(({ ssoStore }) => {
spCertificate,
spPrivateKey,
isGeneratedCertificate,
isCertificateLoading,
};
})(observer(AddSpCertificateModal));

View File

@ -56,15 +56,21 @@ const CertificatesTable = (props) => {
return (
<div key={`certificate-${index}`} className="row">
<ReactSVG src="/static/images/icons/24/file.svg" />
<ReactSVG src="/static/images/icons/32/file.svg" />
<div className="column">
<div className="column-row">
<Text fontWeight={600} fontSize="14px" noSelect>
<Text fontWeight={600} fontSize="14px" lineHeight="16px" noSelect>
{certificate.domainName}
</Text>
</div>
<div className="column-row">
<Text color="#a3a9ae" fontSize="12px" fontWeight={600} noSelect>
<Text
color="#a3a9ae"
fontSize="12px"
fontWeight={600}
lineHeight="16px"
noSelect
>
{certificate.action}
{" | "}
{getFullDate(certificate.startDate)}

View File

@ -31,20 +31,19 @@ const DisableSsoConfirmationModal = (props) => {
</ModalDialog.Body>
<ModalDialog.Footer>
<Box displayProp="flex" marginProp="12px 0 4px 0">
<Button
className="ok-button"
label={t("Common:OKButton")}
onClick={confirmDisable}
primary
size="small"
/>
<Button
label={t("Common:CancelButton")}
onClick={closeConfirmationDisableModal}
size="small"
/>
</Box>
<Button
label={t("Common:OKButton")}
onClick={confirmDisable}
primary
scale
size="normalTouchscreen"
/>
<Button
label={t("Common:CancelButton")}
onClick={closeConfirmationDisableModal}
scale
size="normalTouchscreen"
/>
</ModalDialog.Footer>
</StyledModalDialog>
);

View File

@ -10,9 +10,6 @@ const HideButton = (props) => {
const { t } = useTranslation("SingleSignOn");
const { text, label, isAdditionalParameters, value, setHideLabel } = props;
const marginProp = isAdditionalParameters ? null : "24px 0";
const className = isAdditionalParameters
? "hide-additional-button"
: "hide-button";
const onClick = () => {
setHideLabel(label);
@ -31,7 +28,7 @@ const HideButton = (props) => {
</Text>
)}
<Link className={className} isHovered onClick={onClick} type="action">
<Link className="hide-button" isHovered onClick={onClick} type="action">
{value
? isAdditionalParameters
? t("HideAdditionalParameters")

View File

@ -1,21 +1,36 @@
import React from "react";
import { observer } from "mobx-react";
import { useTranslation } from "react-i18next";
import FieldContainer from "@docspace/components/field-container";
import { Text, HelpButton, InputBlock } from "@docspace/components";
import toastr from "@docspace/components/toast/toastr";
import SsoTextInput from "./SsoTextInput";
import copy from "copy-to-clipboard";
const MetadataUrlField = ({ labelText, name, placeholder, tooltipContent }) => {
const { t } = useTranslation("InviteDialog");
const onCopyClick = () => {
copy(placeholder);
toastr.success(t("InviteDialog:LinkCopySuccess"));
};
return (
<FieldContainer
inlineHelpButton
isVertical
labelText={labelText}
place="top"
tooltipContent={tooltipContent}
>
<SsoTextInput isDisabled name={name} placeholder={placeholder} />
</FieldContainer>
<div className="metadata-field">
<Text className="label" fontSize="13px" fontWeight={600}>
{labelText}
<HelpButton tooltipContent={tooltipContent} />
</Text>
<InputBlock
className="input"
isDisabled
name={name}
placeholder={placeholder}
iconName="/static/images/copy.react.svg"
iconSize={16}
onIconClick={onCopyClick}
/>
</div>
);
};

View File

@ -5,8 +5,6 @@ import { useTranslation } from "react-i18next";
import ComboBox from "@docspace/components/combobox";
import FieldContainer from "@docspace/components/field-container";
import StyledInputWrapper from "../styled-containers/StyledInputWrapper";
const ModalComboBox = (props) => {
const { t } = useTranslation("SingleSignOn");
const { isDisabled, spAction, setComboBoxOption, className } = props;
@ -23,17 +21,15 @@ const ModalComboBox = (props) => {
return (
<FieldContainer isVertical labelText={t("UsedFor")} className={className}>
<StyledInputWrapper>
<ComboBox
onSelect={setComboBoxOption}
options={certificateOptions}
scaled
scaledOptions
selectedOption={currentOption}
showDisabledItems
isDisabled={isDisabled}
/>
</StyledInputWrapper>
<ComboBox
onSelect={setComboBoxOption}
options={certificateOptions}
scaled
scaledOptions
selectedOption={currentOption}
showDisabledItems
isDisabled={isDisabled}
/>
</FieldContainer>
);
};

View File

@ -27,20 +27,19 @@ const ResetConfirmationModal = (props) => {
</ModalDialog.Body>
<ModalDialog.Footer>
<Box displayProp="flex" marginProp="12px 0 4px 0">
<Button
className="ok-button"
label={t("Common:OKButton")}
onClick={confirmReset}
primary
size="small"
/>
<Button
label={t("Common:CancelButton")}
onClick={closeResetModal}
size="small"
/>
</Box>
<Button
label={t("Common:OKButton")}
onClick={confirmReset}
primary
scale
size="normalTouchscreen"
/>
<Button
label={t("Common:CancelButton")}
onClick={closeResetModal}
scale
size="normalTouchscreen"
/>
</ModalDialog.Footer>
</StyledModalDialog>
);

View File

@ -45,14 +45,14 @@ const ToggleSSO = (props) => {
}
/>
<Box>
<Text as="span" fontWeight={600} lineHeight="20px" noSelect>
<div className="toggle-caption">
<Text fontWeight={600} lineHeight="20px" noSelect>
{t("TurnOnSSO")}
</Text>
<Text lineHeight="16px" noSelect>
<Text fontSize="12px" fontWeight={400} lineHeight="16px" noSelect>
{t("TurnOnSSOCaption")}
</Text>
</Box>
</div>
</Box>
{confirmationDisableModal && <DisableSsoConfirmationModal />}

View File

@ -43,7 +43,7 @@ const UploadXML = (props) => {
>
<Box alignItems="center" displayProp="flex" flexDirection="row">
<SsoTextInput
maxWidth="300px"
maxWidth="297px"
name="uploadXmlUrl"
placeholder={t("UploadXMLPlaceholder")}
tabIndex={1}

View File

@ -116,6 +116,7 @@ class SsoFormStore {
isSubmitLoading = false;
isGeneratedCertificate = false;
isCertificateLoading = false;
defaultSettings = null;
@ -584,15 +585,22 @@ class SsoFormStore {
},
];
this.isCertificateLoading = true;
try {
const res = await this.validateCertificate(data);
if (!res) throw Error("Invalid certificate");
if (!res) {
this.isCertificateLoading = false;
return;
}
const newCertificates = res.data;
newCertificates.map((cert) => {
this.spCertificates = [...this.spCertificates, cert];
});
this.isCertificateLoading = false;
this.closeSpModal();
} catch (err) {
this.isCertificateLoading = false;
toastr.error(err);
console.error(err);
}
@ -607,15 +615,22 @@ class SsoFormStore {
},
];
this.isCertificateLoading = true;
try {
const res = await this.validateCertificate(data);
if (!res) throw Error("Invalid certificate");
if (!res) {
this.isCertificateLoading = false;
return;
}
const newCertificates = res.data;
newCertificates.map((cert) => {
this.idpCertificates = [...this.idpCertificates, cert];
});
this.isCertificateLoading = false;
this.closeIdpModal();
} catch (err) {
this.isCertificateLoading = false;
toastr.error(err);
console.error(err);
}

View File

@ -18,7 +18,7 @@ import {
const paddingStyles = css`
padding: ${(props) =>
props.settingsStudio
? "0 7px 16px 24px"
? "0 7px 16px 20px"
: props.viewAs === "row"
? "19px 3px 16px 16px"
: "19px 3px 16px 20px"};

View File

@ -149,7 +149,7 @@ HelpButton.defaultProps = {
offsetTop: 0,
offsetBottom: 0,
className: "icon-button",
size: 13,
size: 12,
};
export default HelpButton;

View File

@ -17,7 +17,9 @@ class InputBlock extends React.Component {
super(props);
}
onIconClick = (e) => {
if (typeof this.props.onIconClick === "function" && !this.props.isDisabled)
if (
typeof this.props.onIconClick === "function" /*&& !this.props.isDisabled*/
)
this.props.onIconClick(e);
};
onChange = (e) => {
@ -126,7 +128,7 @@ class InputBlock extends React.Component {
<div className="append">
<StyledIconBlock
className="input-block-icon"
isDisabled={isDisabled}
//isDisabled={isDisabled}
onClick={this.onIconClick}
isClickable={typeof onIconClick === "function"}
>
@ -134,7 +136,7 @@ class InputBlock extends React.Component {
size={iconButtonSize}
iconName={iconName}
isFill={isIconFill}
isDisabled={isDisabled}
//isDisabled={isDisabled}
isClickable={typeof onIconClick === "function"}
/>
</StyledIconBlock>

View File

@ -75,8 +75,9 @@ export const StyledSubmenuItem = styled.div.attrs((props) => ({
display: flex;
gap: 4px;
flex-direction: column;
padding: 4px 14px 0;
padding-top: 4px;
line-height: 20px;
margin-right: 20px;
`;
export const StyledSubmenuItemText = styled.div`
@ -93,8 +94,8 @@ StyledSubmenuItemText.defaultProps = { theme: Base };
export const StyledSubmenuItemLabel = styled.div`
z-index: 1;
width: calc(100% + 28px);
margin-left: -14px;
width: 100%;
//margin-left: -14px;
height: 4px;
bottom: 0px;
border-radius: 4px 4px 0 0;

View File

@ -480,7 +480,7 @@ const Base = {
textColor: black,
textDisableColor: gray,
marginRight: "4px",
marginRight: "8px",
background: white,
disableBackground: grayLight,
@ -971,8 +971,8 @@ const Base = {
iconButton: {
margin: "0",
padding: "0px 8px",
width: "13px",
height: "13px",
width: "12px",
height: "12px",
},
},

View File

@ -478,7 +478,7 @@ const Dark = {
testColor: grayMaxLight,
textDisableColor: "#5c5c5c",
marginRight: "4px",
marginRight: "8px",
background: "#292929",
disableBackground: "#646464",
@ -968,8 +968,8 @@ const Dark = {
iconButton: {
margin: "0",
padding: "0px 8px",
width: "13px",
height: "13px",
width: "12px",
height: "12px",
},
},