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:
@@ -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>
|
||||
|
@@ -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}
|
||||
|
Reference in New Issue
Block a user