mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-25 06:33:48 +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}
|
||||
|
@@ -479,6 +479,9 @@ def multi_adfs_sso(request):
|
||||
if not org_saml_config:
|
||||
render_data['error_msg'] = "Cannot find a SAML/ADFS config for the organization related to domain %s." % domain
|
||||
return render(request, template_name, render_data)
|
||||
if not org_saml_config.domain_verified:
|
||||
render_data['error_msg'] = "The domain %s has not been verified ownership, please login after verification." % domain
|
||||
return render(request, template_name, render_data)
|
||||
org_id = org_saml_config.org_id
|
||||
org = ccnet_api.get_org_by_id(org_id)
|
||||
if not org:
|
||||
|
@@ -1,6 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
import uuid
|
||||
import subprocess
|
||||
import logging
|
||||
from xml.etree import ElementTree
|
||||
from urllib.parse import urlparse
|
||||
@@ -63,8 +65,7 @@ class OrgUploadIdPCertificateView(APIView):
|
||||
fd.write(idp_certificate.read())
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
return Response({'success': True})
|
||||
|
||||
@@ -95,65 +96,51 @@ class OrgSAMLConfigView(APIView):
|
||||
if not metadata_url:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'metadata_url invalid.')
|
||||
|
||||
res = requests.get(metadata_url)
|
||||
root = ElementTree.fromstring(res.text)
|
||||
entity_id = root.attrib.get('entityID', '')
|
||||
if not entity_id:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Not found entityID in metadata.')
|
||||
|
||||
netloc = urlparse(entity_id).netloc
|
||||
domain = '.'.join(netloc.split('.')[1:])
|
||||
if not domain:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid entityID in metadata.')
|
||||
|
||||
# resource check
|
||||
org_id = int(org_id)
|
||||
if not ccnet_api.get_org_by_id(org_id):
|
||||
org = ccnet_api.get_org_by_id(org_id)
|
||||
if not org:
|
||||
error_msg = 'Organization %s not found.' % org_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
# add an org saml config
|
||||
# add or update saml/adfs login config
|
||||
try:
|
||||
saml_comfig = OrgSAMLConfig.objects.add_or_update_saml_config(org_id, metadata_url, domain)
|
||||
saml_config = OrgSAMLConfig.objects.add_or_update_saml_config(org_id, metadata_url)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
return Response({'saml_config': saml_comfig.to_dict()})
|
||||
return Response({'saml_config': saml_config.to_dict()})
|
||||
|
||||
def put(self, request, org_id):
|
||||
# argument check
|
||||
metadata_url = request.data.get('metadata_url', None)
|
||||
if not metadata_url:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'metadata_url invalid.')
|
||||
|
||||
res = requests.get(metadata_url)
|
||||
root = ElementTree.fromstring(res.text)
|
||||
entity_id = root.attrib.get('entityID', '')
|
||||
if not entity_id:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Not found entityID in metadata.')
|
||||
|
||||
netloc = urlparse(entity_id).netloc
|
||||
domain = '.'.join(netloc.split('.')[1:])
|
||||
domain = request.data.get('domain', None)
|
||||
if not domain:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid entityID in metadata.')
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'domain invalid.')
|
||||
|
||||
# resource check
|
||||
org_id = int(org_id)
|
||||
if not ccnet_api.get_org_by_id(org_id):
|
||||
org = ccnet_api.get_org_by_id(org_id)
|
||||
if not org:
|
||||
error_msg = 'Organization %s not found.' % org_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
# update config
|
||||
saml_config = OrgSAMLConfig.objects.get_config_by_org_id(org_id)
|
||||
if not saml_config:
|
||||
error_msg = 'Cannot find a SAML/ADFS config for the organization %s.' % org.org_name
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
# When the domain is updated, the domain ownership needs to be re-verified, so set dns_txt to None
|
||||
try:
|
||||
saml_comfig = OrgSAMLConfig.objects.add_or_update_saml_config(org_id, metadata_url, domain)
|
||||
saml_config.domain = domain
|
||||
saml_config.dns_txt = None
|
||||
saml_config.domain_verified = False
|
||||
saml_config.save()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
return Response({'saml_config': saml_comfig.to_dict()})
|
||||
return Response({'saml_config': saml_config.to_dict()})
|
||||
|
||||
def delete(self, request, org_id):
|
||||
# resource check
|
||||
@@ -169,8 +156,7 @@ class OrgSAMLConfigView(APIView):
|
||||
return Response({'success': True})
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
error_msg = 'Internal Server Error'
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
return Response({'success': True})
|
||||
|
||||
@@ -227,3 +213,87 @@ class OrgUrlPrefixView(APIView):
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
return Response({'org_url_prefix': org_url_prefix})
|
||||
|
||||
|
||||
class OrgVerifyDomain(APIView):
|
||||
|
||||
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
||||
throttle_classes = (UserRateThrottle,)
|
||||
permission_classes = (IsProVersion, IsOrgAdminUser)
|
||||
|
||||
def post(self, request, org_id):
|
||||
# resource check
|
||||
org_id = int(org_id)
|
||||
org = ccnet_api.get_org_by_id(org_id)
|
||||
if not org:
|
||||
error_msg = 'Organization %s not found.' % org_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
saml_config = OrgSAMLConfig.objects.get_config_by_org_id(org_id)
|
||||
if not saml_config:
|
||||
error_msg = 'Cannot find a SAML/ADFS config for the organization %s.' % org.org_name
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
if saml_config.dns_txt:
|
||||
return Response({'dns_txt': saml_config.dns_txt})
|
||||
|
||||
try:
|
||||
dns_txt = 'seafile-site-verification=' + str(uuid.uuid4().hex)
|
||||
saml_config.dns_txt = dns_txt
|
||||
saml_config.save()
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
return Response({'dns_txt': saml_config.dns_txt})
|
||||
|
||||
def put(self, request, org_id):
|
||||
# argument check
|
||||
domain = request.data.get('domain', None)
|
||||
if not domain:
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, 'domain invalid.')
|
||||
|
||||
# resource check
|
||||
org_id = int(org_id)
|
||||
org = ccnet_api.get_org_by_id(org_id)
|
||||
if not org:
|
||||
error_msg = 'Organization %s not found.' % org_id
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
saml_config = OrgSAMLConfig.objects.get_config_by_org_id(org_id)
|
||||
if not saml_config:
|
||||
error_msg = 'Cannot find a SAML/ADFS config for the organization %s.' % org.org_name
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
if saml_config.domain_verified:
|
||||
return Response({'domain_verified': saml_config.domain_verified})
|
||||
|
||||
if not saml_config.dns_txt:
|
||||
error_msg = 'Cannot find dns_txt, please generate dns_txt first.'
|
||||
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
|
||||
|
||||
proc = subprocess.Popen(["nslookup", "-type=TXT", domain], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
try:
|
||||
stdout, stderr = proc.communicate(timeout=60)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
stdout, stderr = proc.communicate()
|
||||
logger.error('Process execution timed out, stdout: %s, stderr: %s' % (stdout, stderr))
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
if stderr:
|
||||
logger.error(stderr)
|
||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
|
||||
|
||||
if saml_config.dns_txt in stdout.decode():
|
||||
saml_config.domain_verified = True
|
||||
saml_config.save()
|
||||
return Response({'domain_verified': saml_config.domain_verified})
|
||||
else:
|
||||
logger.error(stdout)
|
||||
error_msg = "Failed to verify domain ownership. Please make sure you have added " \
|
||||
"the DNS TXT to your domain's DNS records and wait 5 minutes before trying again."
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
@@ -27,7 +27,8 @@ from .api.admin.logo import OrgAdminLogo
|
||||
from .api.admin.statistics import OrgFileOperationsView, OrgTotalStorageView, \
|
||||
OrgActiveUsersView, OrgSystemTrafficView, OrgUserTrafficView, \
|
||||
OrgUserTrafficExcelView, OrgUserStorageExcelView
|
||||
from .api.admin.saml_config import OrgUploadIdPCertificateView, OrgSAMLConfigView, OrgUrlPrefixView
|
||||
from .api.admin.saml_config import OrgUploadIdPCertificateView, OrgSAMLConfigView, OrgUrlPrefixView, \
|
||||
OrgVerifyDomain
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
@@ -62,6 +63,9 @@ urlpatterns = [
|
||||
path('<int:org_id>/admin/url-prefix/',
|
||||
OrgUrlPrefixView.as_view(),
|
||||
name='api-v2.1-org-admin-url-prefix'),
|
||||
path('<int:org_id>/admin/verify-domain/',
|
||||
OrgVerifyDomain.as_view(),
|
||||
name='api-v2.1-org-admin-verify-domain'),
|
||||
|
||||
path('<int:org_id>/admin/logo/', OrgAdminLogo.as_view(), name='api-v2.1-org-admin-logo'),
|
||||
path('<int:org_id>/admin/devices/', OrgAdminDevices.as_view(), name='api-v2.1-org-admin-devices'),
|
||||
|
@@ -84,18 +84,13 @@ class OrgSettings(models.Model):
|
||||
|
||||
|
||||
class OrgSAMLConfigManager(models.Manager):
|
||||
def add_or_update_saml_config(self, org_id, metadata_url=None, domain=None):
|
||||
def add_or_update_saml_config(self, org_id, metadata_url):
|
||||
try:
|
||||
saml_config = self.get(org_id=org_id)
|
||||
except OrgSAMLConfig.DoesNotExist:
|
||||
saml_config = self.model(org_id=org_id)
|
||||
|
||||
if metadata_url:
|
||||
saml_config.metadata_url = metadata_url
|
||||
|
||||
if domain:
|
||||
saml_config.domain = domain
|
||||
|
||||
saml_config.metadata_url = metadata_url
|
||||
saml_config.save(using=self._db)
|
||||
return saml_config
|
||||
|
||||
@@ -117,7 +112,9 @@ class OrgSAMLConfigManager(models.Manager):
|
||||
class OrgSAMLConfig(models.Model):
|
||||
org_id = models.IntegerField(unique=True)
|
||||
metadata_url = models.TextField()
|
||||
domain = models.CharField(max_length=255, unique=True)
|
||||
domain = models.CharField(max_length=255, unique=True, null=True, blank=True)
|
||||
dns_txt = models.CharField(max_length=64, null=True, blank=True)
|
||||
domain_verified = models.BooleanField(default=False, db_index=True)
|
||||
|
||||
objects = OrgSAMLConfigManager()
|
||||
|
||||
@@ -129,6 +126,9 @@ class OrgSAMLConfig(models.Model):
|
||||
'id': self.pk,
|
||||
'org_id': self.org_id,
|
||||
'metadata_url': self.metadata_url,
|
||||
'domain': self.domain,
|
||||
'dns_txt': self.dns_txt,
|
||||
'domain_verified': self.domain_verified,
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user