# 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})