1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-24 21:07:17 +00:00

Custom org saml login domain (#5622)

* add domain verification api

* improve org saml config page

* improve code

* optimize code

* optimize code

* optimize code
This commit is contained in:
WJH
2023-10-24 12:45:13 +08:00
committed by GitHub
parent cdfc590546
commit d0029efc04
6 changed files with 275 additions and 92 deletions

View File

@@ -1,9 +1,11 @@
import React, { Component, Fragment } from 'react';
import { Input, Row, Col, Label } from 'reactstrap';
import { Input, InputGroup, InputGroupAddon, Button, Row, Col, Label } from 'reactstrap';
import PropTypes from 'prop-types';
import { gettext } from '../../utils/constants';
const propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
value: PropTypes.string,
domainVerified: PropTypes.bool,
changeValue: PropTypes.func.isRequired,
displayName: PropTypes.string.isRequired,
};
@@ -12,14 +14,45 @@ class OrgSamlConfigInput extends Component {
constructor(props) {
super(props);
this.state = {
isBtnsShown: false,
value: this.props.value,
};
}
inputValue = (e) => {
this.props.changeValue(e);
componentWillReceiveProps(nextProps) {
this.setState({value: nextProps.value,});
}
toggleBtns = () => {
this.setState({isBtnsShown: !this.state.isBtnsShown});
};
hideBtns = () => {
if (!this.state.isBtnsShown) {
return;
}
if (this.props.value != this.state.value) {
this.setState({value: this.props.value});
}
this.toggleBtns();
};
onInputChange = (e) => {
this.setState({ value: e.target.value });
};
onSubmit = () => {
const value = this.state.value.trim();
if (value != this.props.value) {
this.props.changeValue(value);
}
this.toggleBtns();
};
render() {
const { value, displayName } = this.props;
const { isBtnsShown, value } = this.state;
const { displayName } = this.props;
return (
<Fragment>
<Row className="my-4">
@@ -27,7 +60,22 @@ class OrgSamlConfigInput extends Component {
<Label className="web-setting-label">{displayName}</Label>
</Col>
<Col md="5">
<Input innerRef={input => {this.newInput = input;}} value={value} onChange={this.inputValue}/>
<InputGroup>
<Input type='text' value={value} onChange={this.onInputChange} onFocus={this.toggleBtns} onBlur={this.hideBtns}/>
{this.props.domainVerified &&
<InputGroupAddon addonType="append">
<Button color="success" className="border-0">{gettext('Verified')}</Button>
</InputGroupAddon>
}
</InputGroup>
</Col>
<Col md="4">
{isBtnsShown &&
<Fragment>
<Button className="sf2-icon-tick web-setting-icon-btn web-setting-icon-btn-submit" onMouseDown={this.onSubmit} title={gettext('Submit')}></Button>
<Button className="ml-1 sf2-icon-x2 web-setting-icon-btn web-setting-icon-btn-cancel" title={gettext('Cancel')}></Button>
</Fragment>
}
</Col>
</Row>
</Fragment>

View File

@@ -1,5 +1,6 @@
import React, { Fragment, Component } from 'react';
import { Row, Col, Label, Button } from 'reactstrap';
import { Row, Col, Label, Button, Input, InputGroup, InputGroupAddon } from 'reactstrap';
import copy from 'copy-to-clipboard';
import MainPanelTopbar from './main-panel-topbar';
import toaster from '../../components/toast';
import Loading from '../../components/loading';
@@ -21,6 +22,9 @@ class OrgSAMLConfig extends Component {
newUrlPrefix: '',
orgUrlPrefix: '',
metadataUrl: '',
domain: '',
dns_txt: '',
domain_verified: false,
isBtnsShown: false,
};
}
@@ -49,18 +53,6 @@ class OrgSAMLConfig extends Component {
this.setState({newUrlPrefix: e.target.value});
};
inputMetadataUrl = (e) => {
this.setState({metadataUrl: e.target.value});
};
inputSingleSignOnService = (e) => {
this.setState({singleSignOnService: e.target.value});
};
inputSingleLogoutService = (e) => {
this.setState({singleLogoutService: e.target.value});
};
componentDidMount() {
seafileAPI.orgAdminGetUrlPrefix(orgID).then((res) => {
this.setState({
@@ -70,8 +62,11 @@ class OrgSAMLConfig extends Component {
seafileAPI.orgAdminGetSamlConfig(orgID).then((res) => {
this.setState({
loading: false,
samlConfigID: res.data.saml_config.id || '',
samlConfigID: res.data.saml_config.id,
metadataUrl: res.data.saml_config.metadata_url || '',
domain: res.data.saml_config.domain || '',
dns_txt: res.data.saml_config.dns_txt || '',
domain_verified: res.data.saml_config.domain_verified || false,
});
}).catch(error => {
this.setState({
@@ -110,12 +105,14 @@ class OrgSAMLConfig extends Component {
});
};
addSamlConfig = () => {
const { metadataUrl } = this.state;
seafileAPI.orgAdminAddSamlConfig(orgID, metadataUrl).then((res) => {
updateSamlMetadataUrl = (metadataUrl) => {
seafileAPI.orgAdminUpdateSamlMetadataUrl(orgID, metadataUrl).then((res) => {
this.setState({
samlConfigID: res.data.saml_config.id,
metadataUrl: res.data.saml_config.metadata_url,
metadataUrl: res.data.saml_config.metadata_url || '',
domain: res.data.saml_config.domain || '',
dns_txt: res.data.saml_config.dns_txt || '',
domain_verified: res.data.saml_config.domain_verified || false,
});
toaster.success(gettext('Success'));
}).catch((error) => {
@@ -124,12 +121,14 @@ class OrgSAMLConfig extends Component {
});
};
updateSamlConfig = () => {
const { metadataUrl } = this.state;
seafileAPI.orgAdminUpdateSamlConfig(orgID, metadataUrl).then((res) => {
updateSamlDomain = (domain) => {
seafileAPI.orgAdminUpdateSamlDomain(orgID, domain).then((res) => {
this.setState({
samlConfigID: res.data.saml_config.id,
metadataUrl: res.data.saml_config.metadata_url,
metadataUrl: res.data.saml_config.metadata_url || '',
domain: res.data.saml_config.domain || '',
dns_txt: res.data.saml_config.dns_txt || '',
domain_verified: res.data.saml_config.domain_verified || false,
});
toaster.success(gettext('Success'));
}).catch((error) => {
@@ -143,6 +142,9 @@ class OrgSAMLConfig extends Component {
this.setState({
samlConfigID: '',
metadataUrl: '',
domain: '',
dns_txt: '',
domain_verified: false,
});
toaster.success(gettext('Success'));
}).catch((error) => {
@@ -151,8 +153,35 @@ class OrgSAMLConfig extends Component {
});
};
verifyDomain = () => {
const {domain} = this.state;
seafileAPI.orgAdminVerifyDomain(orgID, domain).then((res) => {
this.setState({domain_verified: res.data.domain_verified});
toaster.success(gettext('Success'));
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
generateDnsTxt = () => {
seafileAPI.orgAdminCreateDnsTxt(orgID).then((res) => {
this.setState({dns_txt: res.data.dns_txt});
toaster.success(gettext('Success'));
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
onCopyDnsTxt = () => {
const {dns_txt} = this.state;
copy(dns_txt);
toaster.success(gettext('DNS TXT is copied to the clipboard.'));
};
render() {
const { loading, errorMsg, samlConfigID, newUrlPrefix, metadataUrl, isBtnsShown } = this.state;
const { loading, errorMsg, newUrlPrefix, metadataUrl, domain, dns_txt, domain_verified, isBtnsShown } = this.state;
return (
<Fragment>
@@ -174,7 +203,7 @@ class OrgSAMLConfig extends Component {
<Label className="web-setting-label">{gettext('Your custom login URL')}</Label>
</Col>
<Col md="5">
{`${serviceURL}/org/custom/`}<input innerRef={input => {this.newInput = input;}} value={newUrlPrefix} onChange={this.inputOrgUrlPrefix} onFocus={this.toggleBtns} onBlur={this.hideBtns}></input>
{`${serviceURL}/org/custom/`}<input value={newUrlPrefix} onChange={this.inputOrgUrlPrefix} onFocus={this.toggleBtns} onBlur={this.hideBtns}></input>
<p className="small text-secondary mt-1">
{gettext('The custom part of the URL should be 6 to 20 characters, and can only contain alphanumeric characters and hyphens.')}
</p>
@@ -188,29 +217,58 @@ class OrgSAMLConfig extends Component {
</Row>
</Fragment>
</Section>
<Section headingText={gettext('Manage SAML Config')}>
<Fragment>
<InputItem
value={metadataUrl}
changeValue={this.inputMetadataUrl}
changeValue={this.updateSamlMetadataUrl}
displayName={gettext('App Federation Metadata URL')}
/>
<InputItem
value={domain}
changeValue={this.updateSamlDomain}
displayName={gettext('Email Domain')}
domainVerified={domain_verified}
/>
<Row className="my-4">
{samlConfigID ?
<Fragment>
<Col md="1">
<Button color="secondary" onClick={this.updateSamlConfig}>{gettext('Update')}</Button>
</Col>
<Col md="1">
<Button color="primary" onClick={this.deleteSamlConfig}>{gettext('Delete')}</Button>
</Col>
</Fragment> :
<Col md="1">
<Button color="secondary" onClick={this.addSamlConfig}>{gettext('Save')}</Button>
</Col>}
<Col md="3">
<Label className="web-setting-label">{gettext('DNS TXT Value')}</Label>
</Col>
<Col md="5">
<InputGroup>
<Input type="text" readOnly={true} value={dns_txt}/>
{(dns_txt && !domain_verified) &&
<InputGroupAddon addonType="append">
<Button color="primary" onClick={this.onCopyDnsTxt} className="border-0">{gettext('Copy')}</Button>
</InputGroupAddon>
}
</InputGroup>
{(!dns_txt && !domain_verified) &&
<p className="small text-secondary mt-1">
{gettext('Generate a domain DNS TXT, then copy it and add it to your domain\'s DNS records, then click the button to verify domain ownership.')}
</p>
}
{(dns_txt && !domain_verified) &&
<p className="small text-secondary mt-1">
{gettext('You must verify domain ownership before Single Sign-On.')}
</p>
}
</Col>
<Col md="4">
{(!dns_txt && !domain_verified) &&
<Button color="secondary" onClick={this.generateDnsTxt}>{gettext('Generate')}</Button>
}
{(dns_txt && !domain_verified) &&
<Button color="secondary" onClick={this.verifyDomain}>{gettext('Verify')}</Button>
}
</Col>
</Row>
</Fragment>
</Section>
<Section headingText={gettext('Upload IdP Files')}>
<FileItem
postFile={this.postIdpCertificate}