1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-08 10:22:46 +00:00

org invite user (#5815)

This commit is contained in:
lian
2023-12-07 22:50:39 +08:00
committed by GitHub
parent 429eecdd06
commit 255da086e6
13 changed files with 298 additions and 48 deletions

View File

@@ -1,46 +1,88 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; import { Button, Modal, Input, ModalHeader, ModalBody, ModalFooter, Label, Form, InputGroup, InputGroupAddon, FormGroup } from 'reactstrap';
import { gettext } from '../../utils/constants'; import { gettext } from '../../utils/constants';
import toaster from '../toast';
import copy from '../copy-to-clipboard';
const propTypes = { const propTypes = {
toggle: PropTypes.func.isRequired, toggle: PropTypes.func.isRequired,
invitationLink: PropTypes.string.isRequired handleSubmit: PropTypes.func.isRequired,
}; };
class InviteUserDialog extends React.Component { class OrgAdminInviteUserDialog extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {
email: '',
errMessage: '',
isAddingUser: false,
};
} }
copyLink = () => { handleSubmit = () => {
copy(this.props.invitationLink); let isValid = this.validateInputParams();
this.props.toggle(); if (isValid) {
const message = gettext('Internal link has been copied to clipboard'); let { email } = this.state;
toaster.success(message, { this.setState({isAddingUser: true});
duration: 2 this.props.handleSubmit(email.trim());
}); }
}
handleKeyPress = (e) => {
e.preventDefault();
if (e.key == 'Enter') {
this.handleSubmit(e);
}
}; };
inputEmail = (e) => {
let email = e.target.value.trim();
this.setState({email: email});
}
toggle = () => {
this.props.toggle();
}
validateInputParams() {
let errMessage;
let email = this.state.email.trim();
if (!email.length) {
errMessage = gettext('email is required');
this.setState({errMessage: errMessage});
return false;
}
return true;
}
render() { render() {
return ( return (
<Modal isOpen={true} toggle={this.props.toggle}> <Modal isOpen={true} toggle={this.toggle}>
<ModalHeader toggle={this.props.toggle}>{gettext('Invite user')}</ModalHeader> <ModalHeader toggle={this.toggle}>{gettext('Invite users')}</ModalHeader>
<ModalBody> <ModalBody>
<p>{gettext('Send the invitation link to the others, and they will be able to join the organization via scanning the QR code.')}</p> <p>{gettext('You can enter multiple emails, separated by commas. An invitation link will be sent to each user.')}</p>
<p>{this.props.invitationLink}</p> <Form>
<FormGroup>
<Label for="emails">{gettext('Emails')}</Label>
<Input
type="text"
id="emails"
placeholder={gettext('Emails, separated by \',\'')}
value={this.state.email || ''}
onChange={this.inputEmail}
/>
</FormGroup>
</Form>
{this.state.errMessage && <Label className="err-message">{this.state.errMessage}</Label>}
</ModalBody> </ModalBody>
<ModalFooter> <ModalFooter>
<Button color="primary" onClick={this.copyLink}>{gettext('Copy')}</Button> <Button color="primary" disabled={this.state.isAddingUser} onClick={this.handleSubmit} className={this.state.isAddingUser ? 'btn-loading' : ''}>{gettext('Submit')}</Button>
</ModalFooter> </ModalFooter>
</Modal> </Modal>
); );
} }
} }
InviteUserDialog.propTypes = propTypes; OrgAdminInviteUserDialog.propTypes = propTypes;
export default InviteUserDialog; export default OrgAdminInviteUserDialog;

View File

@@ -0,0 +1,46 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
import { gettext } from '../../utils/constants';
import toaster from '../toast';
import copy from '../copy-to-clipboard';
const propTypes = {
toggle: PropTypes.func.isRequired,
invitationLink: PropTypes.string.isRequired
};
class OrgAdminInviteUserViaWeiXinDialog extends React.Component {
constructor(props) {
super(props);
}
copyLink = () => {
copy(this.props.invitationLink);
this.props.toggle();
const message = gettext('Internal link has been copied to clipboard');
toaster.success(message), {
duration: 2
};
}
render() {
return (
<Modal isOpen={true}>
<ModalHeader toggle={this.props.toggle}>{'通过微信邀请用户'}</ModalHeader>
<ModalBody>
<p>{'请将邀请链接发送给其他人,这样他们就可以通过扫描链接里的二维码来加入组织。'}</p>
<p>{this.props.invitationLink}</p>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.copyLink}>{gettext('Copy')}</Button>
</ModalFooter>
</Modal>
);
}
}
OrgAdminInviteUserViaWeiXinDialog.propTypes = propTypes;
export default OrgAdminInviteUserViaWeiXinDialog;

View File

@@ -8,10 +8,11 @@ import ModalPortal from '../../components/modal-portal';
import ImportOrgUsersDialog from '../../components/dialog/org-import-users-dialog'; import ImportOrgUsersDialog from '../../components/dialog/org-import-users-dialog';
import AddOrgUserDialog from '../../components/dialog/org-add-user-dialog'; import AddOrgUserDialog from '../../components/dialog/org-add-user-dialog';
import InviteUserDialog from '../../components/dialog/org-admin-invite-user-dialog'; import InviteUserDialog from '../../components/dialog/org-admin-invite-user-dialog';
import InviteUserViaWeiXinDialog from '../../components/dialog/org-admin-invite-user-via-weixin-dialog';
import toaster from '../../components/toast'; import toaster from '../../components/toast';
import { seafileAPI } from '../../utils/seafile-api'; import { seafileAPI } from '../../utils/seafile-api';
import OrgUserInfo from '../../models/org-user'; import OrgUserInfo from '../../models/org-user';
import { gettext, invitationLink, orgID, siteRoot } from '../../utils/constants'; import { gettext, invitationLink, orgID, siteRoot, orgEnableAdminInviteUser} from '../../utils/constants';
import { Utils } from '../../utils/utils'; import { Utils } from '../../utils/utils';
class Search extends React.Component { class Search extends React.Component {
@@ -80,7 +81,8 @@ class OrgUsers extends Component {
sortOrder: 'asc', sortOrder: 'asc',
isShowAddOrgUserDialog: false, isShowAddOrgUserDialog: false,
isImportOrgUsersDialogOpen: false, isImportOrgUsersDialogOpen: false,
isInviteUserDialogOpen: false isInviteUserDialogOpen: false,
isInviteUserViaWeiXinDialogOpen: false
}; };
} }
@@ -130,6 +132,10 @@ class OrgUsers extends Component {
this.setState({isInviteUserDialogOpen: !this.state.isInviteUserDialogOpen}); this.setState({isInviteUserDialogOpen: !this.state.isInviteUserDialogOpen});
}; };
toggleInviteUserViaWeiXinDialog = () => {
this.setState({isInviteUserViaWeiXinDialogOpen: !this.state.isInviteUserViaWeiXinDialogOpen});
}
initOrgUsersData = (page) => { initOrgUsersData = (page) => {
const { sortBy, sortOrder } = this.state; const { sortBy, sortOrder } = this.state;
seafileAPI.orgAdminListOrgUsers(orgID, '', page, sortBy, sortOrder).then(res => { seafileAPI.orgAdminListOrgUsers(orgID, '', page, sortBy, sortOrder).then(res => {
@@ -202,6 +208,33 @@ class OrgUsers extends Component {
}); });
}; };
inviteOrgUser = (emails) => {
seafileAPI.orgAdminInviteOrgUsers(orgID, emails.split(',')).then(res => {
this.toggleInviteUserDialog();
let users = res.data.success.map(user => {
return new OrgUserInfo(user);
});
this.setState({
orgUsers: users.concat(this.state.orgUsers)
});
res.data.success.map(item => {
let msg = gettext('successfully sent email to %s.');
msg = msg.replace('%s', item.email);
toaster.success(msg);
});
res.data.failed.map(item => {
const msg = `${item.email}: ${item.error_msg}`;
toaster.danger(msg);
});
}).catch(error => {
this.toggleInviteUserDialog();
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
changeStatus= (email, isActive) => { changeStatus= (email, isActive) => {
seafileAPI.orgAdminChangeOrgUserStatus(orgID, email, isActive).then(res => { seafileAPI.orgAdminChangeOrgUserStatus(orgID, email, isActive).then(res => {
let users = this.state.orgUsers.map(item => { let users = this.state.orgUsers.map(item => {
@@ -234,12 +267,16 @@ class OrgUsers extends Component {
let topbarChildren; let topbarChildren;
topbarChildren = ( topbarChildren = (
<Fragment> <Fragment>
<button className="btn btn-secondary operation-item" onClick={this.toggleImportOrgUsersDialog}>{gettext('Import Users')}</button> <button className="btn btn-secondary operation-item" onClick={this.toggleImportOrgUsersDialog}>{gettext('Import users')}</button>
<button className={topBtn} title={gettext('Add User')} onClick={this.toggleAddOrgUser}> <button className={topBtn} title={gettext('Add user')} onClick={this.toggleAddOrgUser}>
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add User')}</button> <i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add user')}</button>
{orgEnableAdminInviteUser &&
<button className={topBtn} title={gettext('Invite users')} onClick={this.toggleInviteUserDialog}>
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Invite users')}</button>
}
{invitationLink && {invitationLink &&
<button className={topBtn} title={gettext('Invite user')} onClick={this.toggleInviteUserDialog}> <button className={topBtn} title={'通过微信邀请用户'} onClick={this.toggleInviteUserViaWeiXinDialog}>
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Invite user')}</button> <i className="fas fa-plus-square text-secondary mr-1"></i>{''}</button>
} }
{this.state.isImportOrgUsersDialogOpen && {this.state.isImportOrgUsersDialogOpen &&
<ModalPortal> <ModalPortal>
@@ -253,7 +290,12 @@ class OrgUsers extends Component {
} }
{this.state.isInviteUserDialogOpen && {this.state.isInviteUserDialogOpen &&
<ModalPortal> <ModalPortal>
<InviteUserDialog invitationLink={invitationLink} toggle={this.toggleInviteUserDialog}/> <InviteUserDialog handleSubmit={this.inviteOrgUser} toggle={this.toggleInviteUserDialog}/>
</ModalPortal>
}
{this.state.isInviteUserViaWeiXinDialogOpen &&
<ModalPortal>
<InviteUserViaWeiXinDialog invitationLink={invitationLink} toggle={this.toggleInviteUserViaWeiXinDialog}/>
</ModalPortal> </ModalPortal>
} }
</Fragment> </Fragment>

View File

@@ -175,6 +175,15 @@ class Item extends Component {
case 'Guest': case 'Guest':
translateResult = gettext('Guest'); translateResult = gettext('Guest');
break; break;
case 'guest':
translateResult = gettext('Guest');
break;
case 'Default':
translateResult = gettext('Default');
break;
case 'default':
translateResult = gettext('Default');
break;
} }
return translateResult; return translateResult;
}; };

View File

@@ -148,6 +148,7 @@ export const invitationLink = window.org ? window.org.pageOptions.invitationLink
export const orgMemberQuotaEnabled = window.org ? window.org.pageOptions.orgMemberQuotaEnabled : ''; export const orgMemberQuotaEnabled = window.org ? window.org.pageOptions.orgMemberQuotaEnabled : '';
export const orgEnableAdminCustomLogo = window.org ? window.org.pageOptions.orgEnableAdminCustomLogo === 'True' : false; export const orgEnableAdminCustomLogo = window.org ? window.org.pageOptions.orgEnableAdminCustomLogo === 'True' : false;
export const orgEnableAdminCustomName = window.org ? window.org.pageOptions.orgEnableAdminCustomName === 'True' : false; export const orgEnableAdminCustomName = window.org ? window.org.pageOptions.orgEnableAdminCustomName === 'True' : false;
export const orgEnableAdminInviteUser = window.org ? window.org.pageOptions.orgEnableAdminInviteUser === 'True' : false;
export const enableMultiADFS = window.org ? window.org.pageOptions.enableMultiADFS === 'True' : false; export const enableMultiADFS = window.org ? window.org.pageOptions.enableMultiADFS === 'True' : false;
// sys admin // sys admin

View File

@@ -10,13 +10,12 @@ from seahub.invitations.settings import INVITATIONS_TOKEN_AGE
from seahub.utils import gen_token, get_site_name from seahub.utils import gen_token, get_site_name
from seahub.utils.timeutils import datetime_to_isoformat_timestr from seahub.utils.timeutils import datetime_to_isoformat_timestr
from seahub.utils.mail import send_html_email_with_dj_template from seahub.utils.mail import send_html_email_with_dj_template
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE, \
GUEST_USER, DEFAULT_USER
GUEST = 'Guest'
class InvitationManager(models.Manager): class InvitationManager(models.Manager):
def add(self, inviter, accepter, invite_type=GUEST): def add(self, inviter, accepter, invite_type=GUEST_USER):
token = gen_token(max_length=32) token = gen_token(max_length=32)
expire_at = timezone.now() + timedelta(hours=int(INVITATIONS_TOKEN_AGE)) expire_at = timezone.now() + timedelta(hours=int(INVITATIONS_TOKEN_AGE))
@@ -40,7 +39,8 @@ class InvitationManager(models.Manager):
class Invitation(models.Model): class Invitation(models.Model):
INVITE_TYPE_CHOICES = ( INVITE_TYPE_CHOICES = (
(GUEST, 'Guest'), (GUEST_USER, 'guest'),
(DEFAULT_USER, 'default'),
) )
token = models.CharField(max_length=40, db_index=True) token = models.CharField(max_length=40, db_index=True)
@@ -48,7 +48,7 @@ class Invitation(models.Model):
accepter = LowerCaseCharField(max_length=255) accepter = LowerCaseCharField(max_length=255)
invite_type = models.CharField(max_length=20, invite_type = models.CharField(max_length=20,
choices=INVITE_TYPE_CHOICES, choices=INVITE_TYPE_CHOICES,
default=GUEST) default=GUEST_USER)
invite_time = models.DateTimeField(auto_now_add=True) invite_time = models.DateTimeField(auto_now_add=True)
accept_time = models.DateTimeField(null=True, blank=True) accept_time = models.DateTimeField(null=True, blank=True)
expire_time = models.DateTimeField() expire_time = models.DateTimeField()
@@ -77,12 +77,12 @@ class Invitation(models.Model):
} }
def is_guest(self): def is_guest(self):
return self.invite_type == GUEST return self.invite_type == GUEST_USER
def is_expired(self): def is_expired(self):
return timezone.now() >= self.expire_time return timezone.now() >= self.expire_time
def send_to(self, email=None): def send_to(self, email=None, org_name=None):
""" """
Send an invitation email to ``email``. Send an invitation email to ``email``.
""" """
@@ -94,6 +94,10 @@ class Invitation(models.Model):
subject = _('You are invited to join %(site_name)s.') % {'site_name': get_site_name()} subject = _('You are invited to join %(site_name)s.') % {'site_name': get_site_name()}
if org_name:
subject = _(f'You are invited to join team {org_name}.')
context['org_name'] = org_name
return send_html_email_with_dj_template(email, return send_html_email_with_dj_template(email,
subject=subject, subject=subject,
dj_template='invitations/invitation_email.html', dj_template='invitations/invitation_email.html',

View File

@@ -9,7 +9,11 @@
<p style="color:#121214;font-size:14px;">{% trans "Hi," %}</p> <p style="color:#121214;font-size:14px;">{% trans "Hi," %}</p>
<p style="font-size:14px;color:#434144;"> <p style="font-size:14px;color:#434144;">
{% blocktrans with inviter_name=inviter|email2nickname %}{{ inviter_name }} invited you to join {{ site_name }}. Please click the link below:{% endblocktrans %} {% if org_name %}
{% blocktrans with org_name=org_name|escape %}You are invited to join team {{ org_name }}. Please click the link below:{% endblocktrans %}
{% else %}
{% blocktrans with inviter_name=inviter|email2nickname|escape %}{{ inviter_name }} invited you to join {{ site_name }}. Please click the link below:{% endblocktrans %}
{% endif %}
</p> </p>
<a href="{{ url_base }}{% url 'invitations:token_view' token %}" target="_blank">{{ url_base }}{% url 'invitations:token_view' token %}</a> <a href="{{ url_base }}{% url 'invitations:token_view' token %}" target="_blank">{{ url_base }}{% url 'invitations:token_view' token %}</a>

View File

@@ -26,18 +26,24 @@ from seahub.utils import is_valid_email, IS_EMAIL_CONFIGURED, \
get_file_type_and_ext get_file_type_and_ext
from seahub.utils.file_size import get_file_size_unit from seahub.utils.file_size import get_file_size_unit
from seahub.utils.error_msg import file_type_error_msg from seahub.utils.error_msg import file_type_error_msg
from seahub.utils.timeutils import timestamp_to_isoformat_timestr, datetime_to_isoformat_timestr from seahub.utils.timeutils import timestamp_to_isoformat_timestr, \
datetime_to_isoformat_timestr
from seahub.utils.licenseparse import user_number_over_limit from seahub.utils.licenseparse import user_number_over_limit
from seahub.views.sysadmin import send_user_add_mail from seahub.views.sysadmin import send_user_add_mail
from seahub.avatar.settings import AVATAR_DEFAULT_SIZE from seahub.avatar.settings import AVATAR_DEFAULT_SIZE
from seahub.avatar.templatetags.avatar_tags import api_avatar_url from seahub.avatar.templatetags.avatar_tags import api_avatar_url
from seahub.invitations.models import Invitation
from seahub.constants import DEFAULT_USER
from pysearpc import SearpcError from pysearpc import SearpcError
import seahub.settings as settings import seahub.settings as settings
from seahub.organizations.settings import ORG_MEMBER_QUOTA_ENABLED from seahub.organizations.models import OrgMemberQuota
from seahub.organizations.views import get_org_user_self_usage, get_org_user_quota, \ from seahub.organizations.settings import ORG_MEMBER_QUOTA_ENABLED, \
is_org_staff, org_user_exists, unset_org_user, set_org_user, set_org_staff, unset_org_staff ORG_ENABLE_ADMIN_INVITE_USER
from seahub.organizations.views import get_org_user_self_usage, \
get_org_user_quota, is_org_staff, org_user_exists, \
unset_org_user, set_org_user, set_org_staff, unset_org_staff
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -223,7 +229,6 @@ class OrgAdminUsers(APIView):
org_members = len(ccnet_api.get_org_users_by_url_prefix(url_prefix, -1, -1)) org_members = len(ccnet_api.get_org_users_by_url_prefix(url_prefix, -1, -1))
if ORG_MEMBER_QUOTA_ENABLED: if ORG_MEMBER_QUOTA_ENABLED:
from seahub.organizations.models import OrgMemberQuota
org_members_quota = OrgMemberQuota.objects.get_quota(request.user.org.org_id) org_members_quota = OrgMemberQuota.objects.get_quota(request.user.org.org_id)
if org_members_quota is not None and org_members >= org_members_quota: if org_members_quota is not None and org_members >= org_members_quota:
err_msg = 'Failed. You can only invite %d members.' % org_members_quota err_msg = 'Failed. You can only invite %d members.' % org_members_quota
@@ -758,3 +763,92 @@ class OrgAdminImportUsers(APIView):
result['success'].append(info) result['success'].append(info)
return Response(result) return Response(result)
class OrgAdminInviteUser(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
throttle_classes = (UserRateThrottle,)
permission_classes = (IsProVersion, IsOrgAdminUser)
def post(self, request, org_id):
"""Invite organization user
"""
if not ORG_ENABLE_ADMIN_INVITE_USER:
error_msg = _('Feature disabled.')
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
if not IS_EMAIL_CONFIGURED:
error_msg = _('Failed to send email, email service is not properly configured, \
please contact administrator.')
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
# parameter check
email_list = request.data.getlist("email", None)
if not email_list:
error_msg = 'email invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
org_id = int(org_id)
org = ccnet_api.get_org_by_id(org_id)
if not org:
error_msg = f'Organization {org_id} not found.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
# check plan
url_prefix = request.user.org.url_prefix
org_members = len(ccnet_api.get_org_users_by_url_prefix(url_prefix, -1, -1))
if ORG_MEMBER_QUOTA_ENABLED:
org_members_quota = OrgMemberQuota.objects.get_quota(request.user.org.org_id)
if org_members_quota is not None and \
org_members + len(email_list) > org_members_quota:
err_msg = f'Failed. You can only invite {org_members_quota} members.'
return api_error(status.HTTP_403_FORBIDDEN, err_msg)
if user_number_over_limit(len(email_list)):
return api_error(status.HTTP_403_FORBIDDEN, 'The number of users exceeds the limit')
username = request.user.username
quota_total = seafile_api.get_org_quota(org_id)
# add user
result = {}
result['failed'] = []
result['success'] = []
for email in email_list:
try:
User.objects.get(email=email)
result['failed'].append({
'email': email,
'error_msg': f'User {email} already exists.'
})
continue
except User.DoesNotExist:
new_user = User.objects.create_user(email, '!',
is_staff=False,
is_active=False)
set_org_user(org_id, email)
# send invitation link
i = Invitation.objects.add(inviter=username,
accepter=email,
invite_type=DEFAULT_USER)
i.send_to(email=email, org_name=org.org_name)
user_info = {}
user_info['email'] = email
user_info['name'] = email2nickname(email)
user_info['contact_email'] = email2contact_email(email)
user_info['is_staff'] = False
user_info['is_active'] = False
user_info['create_time'] = timestamp_to_isoformat_timestr(new_user.ctime)
user_info['quota_usage'] = 0
user_info['quota_total'] = quota_total
user_info['last_login'] = ''
result['success'].append(user_info)
return Response(result)

View File

@@ -11,7 +11,7 @@ from .api.group_owned_libraries import (
) )
from .api.group_members import AdminGroupMembers, AdminGroupMember from .api.group_members import AdminGroupMembers, AdminGroupMember
from .api.admin.users import OrgAdminUser, OrgAdminUsers, OrgAdminSearchUser, \ from .api.admin.users import OrgAdminUser, OrgAdminUsers, OrgAdminSearchUser, \
OrgAdminImportUsers OrgAdminImportUsers, OrgAdminInviteUser
from .api.admin.user_set_password import OrgAdminUserSetPassword from .api.admin.user_set_password import OrgAdminUserSetPassword
from .api.admin.groups import OrgAdminGroups, OrgAdminGroup, OrgAdminSearchGroup from .api.admin.groups import OrgAdminGroups, OrgAdminGroup, OrgAdminSearchGroup
from .api.admin.repos import OrgAdminRepos, OrgAdminRepo from .api.admin.repos import OrgAdminRepos, OrgAdminRepo
@@ -87,6 +87,7 @@ urlpatterns = [
path('<int:org_id>/admin/groups/<int:group_id>/members/<str:email>/', AdminGroupMember.as_view(), name='api-admin-group-member'), path('<int:org_id>/admin/groups/<int:group_id>/members/<str:email>/', AdminGroupMember.as_view(), name='api-admin-group-member'),
path('<int:org_id>/admin/users/', OrgAdminUsers.as_view(), name='api-v2.1-org-admin-users'), path('<int:org_id>/admin/users/', OrgAdminUsers.as_view(), name='api-v2.1-org-admin-users'),
path('<int:org_id>/admin/import-users/', OrgAdminImportUsers.as_view(), name='api-v2.1-org-admin-import-users'), path('<int:org_id>/admin/import-users/', OrgAdminImportUsers.as_view(), name='api-v2.1-org-admin-import-users'),
path('<int:org_id>/admin/invite-users/', OrgAdminInviteUser.as_view(), name='api-v2.1-org-admin-invite-users'),
path('<int:org_id>/admin/search-user/', OrgAdminSearchUser.as_view(), name='api-v2.1-org-admin-search-user'), path('<int:org_id>/admin/search-user/', OrgAdminSearchUser.as_view(), name='api-v2.1-org-admin-search-user'),
path('<int:org_id>/admin/users/<str:email>/', OrgAdminUser.as_view(), name='api-v2.1-org-admin-user'), path('<int:org_id>/admin/users/<str:email>/', OrgAdminUser.as_view(), name='api-v2.1-org-admin-user'),
re_path(r'^(?P<org_id>\d+)/admin/users/(?P<email>[^/]+)/set-password/', OrgAdminUserSetPassword.as_view(), name='api-v2.1-org-admin-user-reset-password'), re_path(r'^(?P<org_id>\d+)/admin/users/(?P<email>[^/]+)/set-password/', OrgAdminUserSetPassword.as_view(), name='api-v2.1-org-admin-user-reset-password'),
@@ -102,3 +103,4 @@ urlpatterns = [
path('admin/logs/file-update/', OrgAdminLogsFileUpdate.as_view(), name='api-v2.1-org-admin-logs-file-update'), path('admin/logs/file-update/', OrgAdminLogsFileUpdate.as_view(), name='api-v2.1-org-admin-logs-file-update'),
path('admin/logs/repo-permission/', OrgAdminLogsPermAudit.as_view(), name='api-v2.1-org-admin-logs-repo-permission'), path('admin/logs/repo-permission/', OrgAdminLogsPermAudit.as_view(), name='api-v2.1-org-admin-logs-repo-permission'),
] ]

View File

@@ -13,7 +13,10 @@ ORG_TRIAL_DAYS = getattr(settings, 'ORG_TRIAL_DAYS', -1)
ORG_AUTO_URL_PREFIX = getattr(settings, 'ORG_AUTO_URL_PREFIX', True) ORG_AUTO_URL_PREFIX = getattr(settings, 'ORG_AUTO_URL_PREFIX', True)
ORG_ENABLE_ADMIN_INVITE_USER = getattr(settings, 'ORG_ENABLE_ADMIN_INVITE_USER', False) ORG_ENABLE_ADMIN_INVITE_USER_VIA_WEIXIN = getattr(settings,
'ORG_ENABLE_ADMIN_INVITE_USER_VIA_WEIXIN',
False)
ORG_ENABLE_ADMIN_INVITE_USER = getattr(settings, 'ORG_ENABLE_ADMIN_INVITE_USER', True)
ORG_ENABLE_ADMIN_CUSTOM_NAME = getattr(settings, 'ORG_ENABLE_ADMIN_CUSTOM_NAME', True) ORG_ENABLE_ADMIN_CUSTOM_NAME = getattr(settings, 'ORG_ENABLE_ADMIN_CUSTOM_NAME', True)
ORG_ENABLE_ADMIN_CUSTOM_LOGO = getattr(settings, 'ORG_ENABLE_ADMIN_CUSTOM_LOGO', True) ORG_ENABLE_ADMIN_CUSTOM_LOGO = getattr(settings, 'ORG_ENABLE_ADMIN_CUSTOM_LOGO', True)

View File

@@ -16,6 +16,7 @@
orgMemberQuotaEnabled: '{{ org_member_quota_enabled }}', orgMemberQuotaEnabled: '{{ org_member_quota_enabled }}',
orgEnableAdminCustomLogo: '{{ org_enable_admin_custom_logo }}', orgEnableAdminCustomLogo: '{{ org_enable_admin_custom_logo }}',
orgEnableAdminCustomName: '{{ org_enable_admin_custom_name }}', orgEnableAdminCustomName: '{{ org_enable_admin_custom_name }}',
orgEnableAdminInviteUser: '{{ org_enable_admin_invite_user }}',
enableMultiADFS: '{{ enable_multi_adfs }}', enableMultiADFS: '{{ enable_multi_adfs }}',
} }
} }

View File

@@ -31,8 +31,9 @@ from seahub.organizations.signals import org_created
from seahub.organizations.decorators import org_staff_required from seahub.organizations.decorators import org_staff_required
from seahub.organizations.forms import OrgRegistrationForm from seahub.organizations.forms import OrgRegistrationForm
from seahub.organizations.settings import ORG_AUTO_URL_PREFIX, \ from seahub.organizations.settings import ORG_AUTO_URL_PREFIX, \
ORG_MEMBER_QUOTA_ENABLED, ORG_ENABLE_ADMIN_INVITE_USER, \ ORG_MEMBER_QUOTA_ENABLED, ORG_ENABLE_ADMIN_INVITE_USER_VIA_WEIXIN, \
ORG_ENABLE_ADMIN_CUSTOM_LOGO, ORG_ENABLE_ADMIN_CUSTOM_NAME ORG_ENABLE_ADMIN_CUSTOM_LOGO, ORG_ENABLE_ADMIN_CUSTOM_NAME, \
ORG_ENABLE_ADMIN_INVITE_USER
from seahub.organizations.utils import get_or_create_invitation_link from seahub.organizations.utils import get_or_create_invitation_link
# Get an instance of a logger # Get an instance of a logger
@@ -247,7 +248,7 @@ def react_fake_view(request, **kwargs):
group_id = kwargs.get('group_id', '') group_id = kwargs.get('group_id', '')
org = request.user.org org = request.user.org
invitation_link = get_or_create_invitation_link(org.org_id) if ORG_ENABLE_ADMIN_INVITE_USER else '' invitation_link = get_or_create_invitation_link(org.org_id) if ORG_ENABLE_ADMIN_INVITE_USER_VIA_WEIXIN else ''
# Whether use new page # Whether use new page
return render(request, "organizations/org_admin_react.html", { return render(request, "organizations/org_admin_react.html", {
@@ -255,6 +256,7 @@ def react_fake_view(request, **kwargs):
'org_member_quota_enabled': ORG_MEMBER_QUOTA_ENABLED, 'org_member_quota_enabled': ORG_MEMBER_QUOTA_ENABLED,
'org_enable_admin_custom_logo': ORG_ENABLE_ADMIN_CUSTOM_LOGO, 'org_enable_admin_custom_logo': ORG_ENABLE_ADMIN_CUSTOM_LOGO,
'org_enable_admin_custom_name': ORG_ENABLE_ADMIN_CUSTOM_NAME, 'org_enable_admin_custom_name': ORG_ENABLE_ADMIN_CUSTOM_NAME,
'org_enable_admin_invite_user': ORG_ENABLE_ADMIN_INVITE_USER,
'group_id': group_id, 'group_id': group_id,
'invitation_link': invitation_link, 'invitation_link': invitation_link,
'enable_multi_adfs': ENABLE_MULTI_ADFS, 'enable_multi_adfs': ENABLE_MULTI_ADFS,

View File

@@ -41,7 +41,7 @@ class InvitationsTest(BaseTestCase):
entry = models.Invitation(token=models.gen_token(max_length=32), entry = models.Invitation(token=models.gen_token(max_length=32),
inviter=self.admin, inviter=self.admin,
accepter=email, accepter=email,
invite_type=models.GUEST, invite_type=models.GUEST_USER,
expire_time=timezone.now()) expire_time=timezone.now())
entry.save() entry.save()
@@ -80,7 +80,7 @@ class InvitationTest(BaseTestCase):
entry = models.Invitation(token=token, entry = models.Invitation(token=token,
inviter=self.admin, inviter=self.admin,
accepter=email, accepter=email,
invite_type=models.GUEST, invite_type=models.GUEST_USER,
expire_time=timezone.now()) expire_time=timezone.now())
entry.save() entry.save()
return token return token