mirror of
https://github.com/haiwen/seahub.git
synced 2025-04-27 02:51:00 +00:00
* send subscription expire notification * optimize code --------- Co-authored-by: 孙永强 <11704063+s-yongqiang@user.noreply.gitee.com>
541 lines
18 KiB
Python
541 lines
18 KiB
Python
# Copyright (c) 2012-2016 Seafile Ltd.
|
|
import logging
|
|
|
|
from django.utils.crypto import get_random_string
|
|
|
|
from rest_framework.authentication import SessionAuthentication
|
|
from rest_framework.permissions import IsAdminUser
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
from rest_framework import status
|
|
|
|
from seaserv import ccnet_api, seafile_api
|
|
|
|
from seahub.auth.utils import get_virtual_id_by_email
|
|
from seahub.organizations.settings import ORG_MEMBER_QUOTA_DEFAULT
|
|
from seahub.utils import is_valid_email
|
|
from seahub.utils.file_size import get_file_size_unit
|
|
from seahub.utils.timeutils import timestamp_to_isoformat_timestr, datetime_to_isoformat_timestr
|
|
from seahub.base.templatetags.seahub_tags import email2nickname, \
|
|
email2contact_email
|
|
from seahub.base.accounts import User
|
|
from seahub.base.models import OrgLastActivityTime
|
|
from seahub.api2.authentication import TokenAuthentication
|
|
from seahub.api2.throttling import UserRateThrottle
|
|
from seahub.api2.utils import api_error, to_python_boolean
|
|
from seahub.api2.permissions import IsProVersion
|
|
from seahub.role_permissions.utils import get_available_roles
|
|
from seahub.organizations.models import OrgSAMLConfig
|
|
from seahub.utils.ccnet_db import CcnetDB
|
|
|
|
|
|
try:
|
|
from seahub.settings import ORG_MEMBER_QUOTA_ENABLED
|
|
except ImportError:
|
|
ORG_MEMBER_QUOTA_ENABLED = False
|
|
|
|
if ORG_MEMBER_QUOTA_ENABLED:
|
|
from seahub.organizations.models import OrgMemberQuota
|
|
|
|
try:
|
|
from seahub.settings import MULTI_TENANCY
|
|
from seahub.organizations.models import OrgSettings
|
|
except ImportError:
|
|
MULTI_TENANCY = False
|
|
|
|
try:
|
|
from seahub.settings import ENABLE_MULTI_ADFS
|
|
except ImportError:
|
|
ENABLE_MULTI_ADFS = False
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_org_info(org):
|
|
org_id = org.org_id
|
|
|
|
org_info = {}
|
|
org_info['org_id'] = org_id
|
|
org_info['org_name'] = org.org_name
|
|
org_info['ctime'] = timestamp_to_isoformat_timestr(org.ctime)
|
|
org_info['org_url_prefix'] = org.url_prefix
|
|
org_info['role'] = OrgSettings.objects.get_role_by_org(org)
|
|
org_info['is_active'] = OrgSettings.objects.get_is_active_by_org(org)
|
|
|
|
creator = org.creator
|
|
org_info['creator_email'] = creator
|
|
org_info['creator_name'] = email2nickname(creator)
|
|
org_info['creator_contact_email'] = email2contact_email(creator)
|
|
|
|
org_info['quota'] = seafile_api.get_org_quota(org_id)
|
|
org_info['quota_usage'] = seafile_api.get_org_quota_usage(org_id)
|
|
|
|
if ORG_MEMBER_QUOTA_ENABLED:
|
|
org_info['max_user_number'] = OrgMemberQuota.objects.get_quota(org_id)
|
|
|
|
return org_info
|
|
|
|
|
|
def get_org_detailed_info(org):
|
|
|
|
org_id = org.org_id
|
|
org_info = get_org_info(org)
|
|
|
|
# users
|
|
users = ccnet_api.get_org_emailusers(org.url_prefix, -1, -1)
|
|
org_info['users_count'] = len(users)
|
|
|
|
active_users_count = len([m for m in users if m.is_active])
|
|
org_info['active_users_count'] = active_users_count
|
|
|
|
repos = seafile_api.get_org_repo_list(org_id, -1, -1)
|
|
org_info['repos_count'] = len(repos)
|
|
|
|
# groups
|
|
groups = ccnet_api.get_org_groups(org_id, -1, -1)
|
|
org_info['groups_count'] = len(groups)
|
|
|
|
# saml config
|
|
org_info['enable_saml_login'] = False
|
|
if ENABLE_MULTI_ADFS:
|
|
org_saml_config = OrgSAMLConfig.objects.get_config_by_org_id(org_id)
|
|
if org_saml_config:
|
|
org_info['enable_saml_login'] = True
|
|
org_info['metadata_url'] = org_saml_config.metadata_url
|
|
org_info['domain'] = org_saml_config.domain
|
|
|
|
return org_info
|
|
|
|
|
|
def gen_org_url_prefix(max_trial=None, length=20):
|
|
"""Generate organization url prefix automatically.
|
|
If ``max_trial`` is large than 0, then re-try that times if failed.
|
|
|
|
Arguments:
|
|
- `max_trial`:
|
|
|
|
Returns:
|
|
Url prefix if succed, otherwise, ``None``.
|
|
"""
|
|
def _gen_prefix():
|
|
url_prefix = 'org-' + get_random_string(
|
|
length, allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789')
|
|
if ccnet_api.get_org_by_url_prefix(url_prefix) is not None:
|
|
logger.error("org url prefix, %s is duplicated" % url_prefix)
|
|
return None
|
|
else:
|
|
return url_prefix
|
|
|
|
try:
|
|
max_trial = int(max_trial)
|
|
except (TypeError, ValueError):
|
|
max_trial = 0
|
|
|
|
while max_trial >= 0:
|
|
ret = _gen_prefix()
|
|
if ret is not None:
|
|
return ret
|
|
else:
|
|
max_trial -= 1
|
|
|
|
logger.error("Failed to generate org url prefix, retry: %d" % max_trial)
|
|
return None
|
|
|
|
|
|
class AdminOrganizations(APIView):
|
|
|
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
|
permission_classes = (IsAdminUser, IsProVersion)
|
|
throttle_classes = (UserRateThrottle,)
|
|
|
|
def get(self, request):
|
|
""" Get all organizations
|
|
|
|
Permission checking:
|
|
1. only admin can perform this action.
|
|
"""
|
|
|
|
if not MULTI_TENANCY:
|
|
error_msg = 'Feature is not enabled.'
|
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
|
|
|
if not request.user.admin_permissions.other_permission():
|
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
|
|
|
try:
|
|
page = int(request.GET.get('page', '1'))
|
|
per_page = int(request.GET.get('per_page', '25'))
|
|
except ValueError:
|
|
page = 1
|
|
per_page = 25
|
|
|
|
start = (page - 1) * per_page
|
|
|
|
try:
|
|
orgs = ccnet_api.get_all_orgs(start, per_page)
|
|
total_count = ccnet_api.count_orgs()
|
|
except Exception as e:
|
|
logger.error(e)
|
|
error_msg = 'Internal Server Error'
|
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
|
|
|
result = []
|
|
org_ids = [org.org_id for org in orgs]
|
|
orgs_last_activity = OrgLastActivityTime.objects.filter(org_id__in=org_ids)
|
|
orgs_last_activity_dict = {org.org_id:org.timestamp for org in orgs_last_activity}
|
|
for org in orgs:
|
|
org_info = get_org_info(org)
|
|
org_id = org_info['org_id']
|
|
if org_id in orgs_last_activity_dict:
|
|
org_info['last_activity_time'] = datetime_to_isoformat_timestr(orgs_last_activity_dict[org_id])
|
|
else:
|
|
org_info['last_activity_time'] = None
|
|
result.append(org_info)
|
|
|
|
return Response({'organizations': result, 'total_count': total_count})
|
|
|
|
def post(self, request):
|
|
""" Create an organization
|
|
|
|
Permission checking:
|
|
1. only admin can perform this action.
|
|
"""
|
|
if not MULTI_TENANCY:
|
|
error_msg = 'Feature is not enabled.'
|
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
|
|
|
if not request.user.admin_permissions.other_permission():
|
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
|
|
|
org_name = request.data.get('org_name', None)
|
|
if not org_name:
|
|
error_msg = 'org_name invalid.'
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
owner_email = request.data.get('owner_email', None)
|
|
if not owner_email or not is_valid_email(owner_email):
|
|
error_msg = 'email invalid.'
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
owner_password = request.data.get('owner_password', None)
|
|
if not owner_password:
|
|
error_msg = 'owner_password invalid.'
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
url_prefix = gen_org_url_prefix(5, 20)
|
|
if ccnet_api.get_org_by_url_prefix(url_prefix):
|
|
error_msg = 'Failed to create organization, please try again later.'
|
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
|
|
|
vid = get_virtual_id_by_email(owner_email)
|
|
try:
|
|
User.objects.get(email=vid)
|
|
except User.DoesNotExist:
|
|
pass
|
|
else:
|
|
error_msg = "User %s already exists." % owner_email
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
try:
|
|
new_user = User.objects.create_user(owner_email, owner_password,
|
|
is_staff=False, is_active=True)
|
|
except User.DoesNotExist as e:
|
|
logger.error(e)
|
|
error_msg = 'Failed to add user %s.' % owner_email
|
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
|
|
|
try:
|
|
org_id = ccnet_api.create_org(org_name, url_prefix, new_user.username)
|
|
except Exception as e:
|
|
logger.error(e)
|
|
error_msg = 'Internal Server Error'
|
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
|
|
|
quota = request.data.get('quota', None)
|
|
if quota:
|
|
try:
|
|
quota_mb = int(quota)
|
|
quota = quota_mb * get_file_size_unit('MB')
|
|
seafile_api.set_org_quota(org_id, quota)
|
|
except ValueError as e:
|
|
logger.error(e)
|
|
return api_error(status.HTTP_400_BAD_REQUEST, "Quota is not valid")
|
|
|
|
if ORG_MEMBER_QUOTA_ENABLED:
|
|
member_limit = request.data.get('member_limit', ORG_MEMBER_QUOTA_DEFAULT)
|
|
OrgMemberQuota.objects.set_quota(org_id, member_limit)
|
|
|
|
org = ccnet_api.get_org_by_id(org_id)
|
|
try:
|
|
org_info = get_org_info(org)
|
|
except Exception as e:
|
|
logger.error(e)
|
|
error_msg = 'Internal Server Error'
|
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
|
|
|
return Response(org_info)
|
|
|
|
|
|
class AdminOrganization(APIView):
|
|
|
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
|
permission_classes = (IsAdminUser, IsProVersion)
|
|
throttle_classes = (UserRateThrottle,)
|
|
|
|
def get(self, request, org_id):
|
|
""" Get base info of a organization
|
|
|
|
Permission checking:
|
|
1. only admin can perform this action.
|
|
"""
|
|
|
|
if not MULTI_TENANCY:
|
|
error_msg = 'Feature is not enabled.'
|
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
|
|
|
if not request.user.admin_permissions.other_permission():
|
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
|
|
|
org_id = int(org_id)
|
|
if org_id == 0:
|
|
error_msg = 'org_id invalid.'
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
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)
|
|
|
|
try:
|
|
org_info = get_org_detailed_info(org)
|
|
except Exception as e:
|
|
logger.error(e)
|
|
error_msg = 'Internal Server Error'
|
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
|
|
|
return Response(org_info)
|
|
|
|
def put(self, request, org_id):
|
|
""" Update base info of a organization
|
|
|
|
Permission checking:
|
|
1. only admin can perform this action.
|
|
"""
|
|
|
|
if not MULTI_TENANCY:
|
|
error_msg = 'Feature is not enabled.'
|
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
|
|
|
if not request.user.admin_permissions.other_permission():
|
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
|
|
|
org_id = int(org_id)
|
|
if org_id == 0:
|
|
error_msg = 'org_id invalid.'
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
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 org name
|
|
new_name = request.data.get('org_name', None)
|
|
if new_name:
|
|
try:
|
|
ccnet_api.set_org_name(org_id, new_name)
|
|
except Exception as e:
|
|
logger.error(e)
|
|
error_msg = 'Internal Server Error'
|
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
|
|
|
# update org max user number
|
|
max_user_number = request.data.get('max_user_number', None)
|
|
if max_user_number and ORG_MEMBER_QUOTA_ENABLED:
|
|
|
|
try:
|
|
max_user_number = int(max_user_number)
|
|
except ValueError:
|
|
error_msg = 'max_user_number invalid.'
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
if max_user_number <= 0:
|
|
error_msg = 'max_user_number invalid.'
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
try:
|
|
OrgMemberQuota.objects.set_quota(org_id, max_user_number)
|
|
except Exception as e:
|
|
logger.error(e)
|
|
error_msg = 'Internal Server Error'
|
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
|
|
|
quota_mb = request.data.get('quota', None)
|
|
if quota_mb:
|
|
|
|
try:
|
|
quota_mb = int(quota_mb)
|
|
except ValueError:
|
|
error_msg = 'quota invalid.'
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
if quota_mb < 0:
|
|
error_msg = 'quota invalid.'
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
quota = quota_mb * get_file_size_unit('MB')
|
|
try:
|
|
seafile_api.set_org_quota(org_id, quota)
|
|
except Exception as e:
|
|
logger.error(e)
|
|
error_msg = 'Internal Server Error'
|
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
|
|
|
role = request.data.get('role', None)
|
|
if role:
|
|
if role not in get_available_roles():
|
|
error_msg = 'Role %s invalid.' % role
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
OrgSettings.objects.add_or_update(org, role)
|
|
|
|
is_active = request.data.get('is_active', None)
|
|
if is_active:
|
|
is_active = is_active.lower()
|
|
if is_active not in ('true', 'false'):
|
|
error_msg = 'is_active invalid.'
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
OrgSettings.objects.add_or_update(org, is_active=is_active == 'true')
|
|
|
|
org = ccnet_api.get_org_by_id(org_id)
|
|
org_info = get_org_info(org)
|
|
return Response(org_info)
|
|
|
|
def delete(self, request, org_id):
|
|
""" Delete an organization
|
|
|
|
Permission checking:
|
|
1. only admin can perform this action.
|
|
"""
|
|
|
|
if not MULTI_TENANCY:
|
|
error_msg = 'Feature is not enabled.'
|
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
|
|
|
if not request.user.admin_permissions.other_permission():
|
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
|
|
|
org_id = int(org_id)
|
|
if org_id == 0:
|
|
error_msg = 'org_id invalid.'
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
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)
|
|
|
|
try:
|
|
# remove org users
|
|
users = ccnet_api.get_org_emailusers(org.url_prefix, -1, -1)
|
|
for u in users:
|
|
ccnet_api.remove_org_user(org_id, u.email)
|
|
User.objects.get(email=u.email).delete()
|
|
|
|
# remove org groups
|
|
groups = ccnet_api.get_org_groups(org_id, -1, -1)
|
|
for g in groups:
|
|
ccnet_api.remove_org_group(org_id, g.gid)
|
|
|
|
# remove org repos
|
|
seafile_api.remove_org_repo_by_org_id(org_id)
|
|
|
|
# remove org saml config
|
|
OrgSAMLConfig.objects.filter(org_id=org_id).delete()
|
|
|
|
# remove org
|
|
ccnet_api.remove_org(org_id)
|
|
except Exception as e:
|
|
logger.error(e)
|
|
error_msg = 'Internal Server Error'
|
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
|
|
|
return Response({'success': True})
|
|
|
|
|
|
class AdminSearchOrganization(APIView):
|
|
|
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
|
permission_classes = (IsAdminUser, IsProVersion)
|
|
throttle_classes = (UserRateThrottle,)
|
|
|
|
def get(self, request):
|
|
""" Search organization by name.
|
|
|
|
Permission checking:
|
|
1. only admin can perform this action.
|
|
"""
|
|
|
|
if not MULTI_TENANCY:
|
|
error_msg = 'Feature is not enabled.'
|
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
|
|
|
if not request.user.admin_permissions.other_permission():
|
|
return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
|
|
|
|
query_str = request.GET.get('query', '').lower().strip()
|
|
if not query_str:
|
|
error_msg = 'query invalid.'
|
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
|
|
|
try:
|
|
orgs = ccnet_api.search_orgs(query_str)
|
|
except Exception as e:
|
|
logger.error(e)
|
|
error_msg = 'Internal Server Error'
|
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
|
|
|
result = []
|
|
for org in orgs:
|
|
org_info = get_org_info(org)
|
|
result.append(org_info)
|
|
|
|
return Response({'organization_list': result})
|
|
|
|
|
|
class AdminOrganizationsBaseInfo(APIView):
|
|
authentication_classes = (TokenAuthentication, SessionAuthentication)
|
|
permission_classes = (IsAdminUser, IsProVersion)
|
|
throttle_classes = (UserRateThrottle,)
|
|
|
|
def get(self, request):
|
|
'''
|
|
Get base info of organizations in bulk by ids
|
|
'''
|
|
if not MULTI_TENANCY:
|
|
error_msg = 'Feature is not enabled.'
|
|
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
|
|
|
org_ids = request.GET.getlist('org_ids',[])
|
|
include_org_staffs = to_python_boolean(request.GET.get('include_org_staffs', 'false'))
|
|
orgs = []
|
|
for org_id in org_ids:
|
|
try:
|
|
org = ccnet_api.get_org_by_id(int(org_id))
|
|
if not org:
|
|
continue
|
|
except Exception:
|
|
continue
|
|
base_info = {'org_id': org.org_id, 'org_name': org.org_name}
|
|
staffs = []
|
|
if include_org_staffs:
|
|
try:
|
|
ccnet_db = CcnetDB()
|
|
staffs = ccnet_db.get_org_staffs(int(org_id))
|
|
except Exception:
|
|
pass
|
|
base_info['org_staffs'] = staffs
|
|
orgs.append(base_info)
|
|
return Response({'organization_list': orgs})
|