diff --git a/seahub/api2/endpoints/admin/users_batch.py b/seahub/api2/endpoints/admin/users_batch.py index 7cc7056753..8f9e7f6da3 100644 --- a/seahub/api2/endpoints/admin/users_batch.py +++ b/seahub/api2/endpoints/admin/users_batch.py @@ -1,6 +1,10 @@ # Copyright (c) 2012-2016 Seafile Ltd. import logging +from io import BytesIO +from openpyxl import load_workbook +from constance import config + from rest_framework import status from rest_framework.authentication import SessionAuthentication from rest_framework.permissions import IsAdminUser @@ -11,6 +15,7 @@ from django.utils.translation import ugettext as _ from seaserv import seafile_api +import seahub.settings as settings from seahub.api2.authentication import TokenAuthentication from seahub.api2.throttling import UserRateThrottle from seahub.api2.utils import api_error @@ -19,14 +24,21 @@ from seahub.base.accounts import User from seahub.profile.models import Profile from seahub.institutions.models import Institution from seahub.utils.file_size import get_file_size_unit -from seahub.utils import is_valid_username -from seahub.admin_log.models import USER_DELETE +from seahub.utils import is_valid_username, get_file_type_and_ext, \ + is_pro_version, get_site_name +from seahub.utils.error_msg import file_type_error_msg +from seahub.utils.licenseparse import user_number_over_limit +from seahub.utils.mail import send_html_email_with_dj_template +from seahub.admin_log.models import USER_DELETE, USER_ADD from seahub.admin_log.signals import admin_operation from seahub.utils.timeutils import timestamp_to_isoformat_timestr, datetime_to_isoformat_timestr from seahub.constants import DEFAULT_ADMIN from seahub.role_permissions.models import AdminRole from seahub.base.templatetags.seahub_tags import email2nickname, email2contact_email from seahub.base.models import UserLastLogin +from seahub.options.models import UserOptions +from seahub.role_permissions.utils import get_available_roles +from seahub.utils.user_permissions import get_user_role logger = logging.getLogger(__name__) @@ -227,3 +239,161 @@ class AdminUsersBatch(APIView): }) return Response(result) + + +class AdminImportUsers(APIView): + authentication_classes = (TokenAuthentication, SessionAuthentication) + throttle_classes = (UserRateThrottle,) + permission_classes = (IsAdminUser,) + + def post(self, request): + """ Import users from xlsx file + + Permission checking: + 1. admin user. + """ + xlsx_file = request.FILES.get('file', None) + if not xlsx_file: + error_msg = 'file can not be found.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + file_type, ext = get_file_type_and_ext(xlsx_file.name) + if ext != 'xlsx': + error_msg = file_type_error_msg(ext, 'xlsx') + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + content = xlsx_file.read() + + try: + fs = BytesIO(content) + wb = load_workbook(filename=fs, read_only=True) + except Exception as e: + logger.error(e) + + # example file is like: + # Email Password Name(Optional) Role(Optional) Space Quota(MB, Optional) + # a@a.com a a default 1024 + # b@b.com b b default 2048 + + rows = wb.worksheets[0].rows + records = [] + # skip first row(head field). + next(rows) + for row in rows: + records.append([col.value for col in row]) + + if user_number_over_limit(new_users=len(records)): + error_msg = 'The number of users exceeds the limit.' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + result = {} + result['failed'] = [] + result['success'] = [] + for record in records: + if record[0]: + email = record[0].strip() + if not is_valid_username(email): + result['failed'].append({ + 'email': email, + 'error_msg': 'email %s invalid.' % email + }) + continue + else: + result['failed'].append({ + 'email': '', + 'error_msg': 'email invalid.' + }) + continue + + if record[1]: + password = record[1].strip() + if not password: + result['failed'].append({ + 'email': email, + 'error_msg': 'password invalid.' + }) + continue + else: + result['failed'].append({ + 'email': email, + 'error_msg': 'password invalid.' + }) + continue + + try: + User.objects.get(email=email) + result['failed'].append({ + 'email': email, + 'error_msg': 'user %s exists.' % email + }) + continue + except User.DoesNotExist: + pass + + User.objects.create_user(email, password, is_staff=False, is_active=True) + if config.FORCE_PASSWORD_CHANGE: + UserOptions.objects.set_force_passwd_change(email) + + # update the user's optional info + # update nikename + if record[2]: + try: + nickname = record[2].strip() + if len(nickname) <= 64 and '/' not in nickname: + Profile.objects.add_or_update(email, nickname, '') + except Exception as e: + logger.error(e) + # update role + if record[3]: + try: + role = record[3].strip() + if is_pro_version() and role in get_available_roles(): + User.objects.update_role(email, role) + except Exception as e: + logger.error(e) + # update quota + if record[4]: + try: + space_quota_mb = int(record[4]) + if space_quota_mb >= 0: + space_quota = int(space_quota_mb) * get_file_size_unit('MB') + seafile_api.set_user_quota(email, space_quota) + except Exception as e: + logger.error(e) + + send_html_email_with_dj_template( + email, dj_template='sysadmin/user_batch_add_email.html', + subject=_('You are invited to join %s') % get_site_name(), + context={ + 'user': email2nickname(request.user.username), + 'email': email, + 'password': password, + }) + + user = User.objects.get(email=email) + + info = {} + info['email'] = email + info['name'] = email2nickname(email) + info['contact_email'] = email2contact_email(email) + + info['is_staff'] = user.is_staff + info['is_active'] = user.is_active + + info['quota_usage'] = seafile_api.get_user_self_usage(user.email) + info['quota_total'] = seafile_api.get_user_quota(user.email) + + info['create_time'] = timestamp_to_isoformat_timestr(user.ctime) + + info['role'] = get_user_role(user) + result['success'].append(info) + + # send admin operation log signal + admin_op_detail = { + "email": email, + } + admin_operation.send(sender=None, admin_name=request.user.username, + operation=USER_ADD, detail=admin_op_detail) + + return Response(result) + diff --git a/seahub/urls.py b/seahub/urls.py index 530b8a9760..17641aa371 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -125,7 +125,8 @@ from seahub.api2.endpoints.admin.share_links import AdminShareLinks, AdminShareL AdminShareLinkDirents from seahub.api2.endpoints.admin.upload_links import AdminUploadLinks, AdminUploadLink, \ AdminUploadLinkUpload, AdminUploadLinkCheckPassword -from seahub.api2.endpoints.admin.users_batch import AdminUsersBatch, AdminAdminUsersBatch +from seahub.api2.endpoints.admin.users_batch import AdminUsersBatch, AdminAdminUsersBatch, \ + AdminImportUsers from seahub.api2.endpoints.admin.operation_logs import AdminOperationLogs from seahub.api2.endpoints.admin.organizations import AdminOrganizations, AdminOrganization from seahub.api2.endpoints.admin.org_users import AdminOrgUsers, AdminOrgUser @@ -527,9 +528,10 @@ urlpatterns = [ url(r'^api/v2.1/admin/upload-links/(?P[a-f0-9]+)/check-password/$', AdminUploadLinkCheckPassword.as_view(), name='api-v2.1-admin-upload-link-check-password'), - ## admin::users + ## admin::users-batch url(r'^api/v2.1/admin/admin-users/batch/$', AdminAdminUsersBatch.as_view(), name='api-v2.1-admin-users-batch'), url(r'^api/v2.1/admin/users/batch/$', AdminUsersBatch.as_view(), name='api-v2.1-admin-users-batch'), + url(r'^api/v2.1/admin/import-users/$', AdminImportUsers.as_view(), name='api-v2.1-admin-import-users'), ## admin::admin-role url(r'^api/v2.1/admin/admin-role/$', AdminAdminRole.as_view(), name='api-v2.1-admin-admin-role'),