1# Copyright (c) 2012-2016 Seafile Ltd.
2# encoding: utf-8
3import logging
4import os
5import stat
6from importlib import import_module
7import json
8import datetime
9import posixpath
10import re
11from dateutil.relativedelta import relativedelta
12from urllib.parse import quote
13
14from rest_framework import parsers
15from rest_framework import status
16from rest_framework import renderers
17from rest_framework.authentication import SessionAuthentication
18from rest_framework.permissions import IsAuthenticated, IsAdminUser
19from rest_framework.reverse import reverse
20from rest_framework.response import Response
21
22from django.conf import settings as dj_settings
23from django.contrib.auth.hashers import check_password
24from django.contrib.sites.shortcuts import get_current_site
25from django.db import IntegrityError
26from django.db.models import F
27from django.http import HttpResponse
28from django.template.defaultfilters import filesizeformat
29from django.utils import timezone
30from django.utils.translation import ugettext as _
31
32from .throttling import ScopedRateThrottle, AnonRateThrottle, UserRateThrottle
33from .authentication import TokenAuthentication
34from .serializers import AuthTokenSerializer
35from .utils import get_diff_details, to_python_boolean, \
36    api_error, get_file_size, prepare_starred_files, is_web_request, \
37    get_groups, api_group_check, get_timestamp, json_response
38from seahub.wopi.utils import get_wopi_dict
39from seahub.api2.base import APIView
40from seahub.api2.models import TokenV2, DESKTOP_PLATFORMS
41from seahub.api2.endpoints.group_owned_libraries import get_group_id_by_repo_owner
42from seahub.avatar.templatetags.avatar_tags import api_avatar_url, avatar
43from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url, \
44        grp_avatar
45from seahub.base.accounts import User
46from seahub.base.models import UserStarredFiles, DeviceToken, RepoSecretKey, FileComment
47from seahub.share.models import ExtraSharePermission, ExtraGroupsSharePermission
48from seahub.share.utils import is_repo_admin, check_group_share_in_permission
49from seahub.base.templatetags.seahub_tags import email2nickname, \
50    translate_seahub_time, translate_commit_desc_escape, \
51    email2contact_email
52from seahub.constants import PERMISSION_READ_WRITE, PERMISSION_PREVIEW_EDIT
53from seahub.group.views import remove_group_common, \
54    rename_group_with_new_name, is_group_staff
55from seahub.group.utils import BadGroupNameError, ConflictGroupNameError, \
56    validate_group_name, is_group_member, group_id_to_name, is_group_admin
57from seahub.thumbnail.utils import generate_thumbnail
58from seahub.notifications.models import UserNotification
59from seahub.options.models import UserOptions
60from seahub.profile.models import Profile, DetailedProfile
61from seahub.drafts.models import Draft
62from seahub.drafts.utils import get_file_draft, \
63    is_draft_file, has_draft_file
64from seahub.signals import (repo_created, repo_deleted, repo_transfer)
65from seahub.share.models import FileShare, OrgFileShare, UploadLinkShare
66from seahub.utils import gen_file_get_url, gen_token, gen_file_upload_url, \
67    check_filename_with_rename, is_valid_username, EVENTS_ENABLED, \
68    get_user_events, EMPTY_SHA1, is_pro_version, \
69    gen_block_get_url, get_file_type_and_ext, HAS_FILE_SEARCH, \
70    gen_file_share_link, gen_dir_share_link, is_org_context, gen_shared_link, \
71    get_org_user_events, calculate_repos_last_modify, send_perm_audit_msg, \
72    gen_shared_upload_link, convert_cmmt_desc_link, is_valid_dirent_name, \
73    normalize_file_path, get_no_duplicate_obj_name, normalize_dir_path
74
75from seahub.utils.file_types import IMAGE
76from seahub.utils.file_revisions import get_file_revisions_after_renamed
77from seahub.utils.devices import do_unlink_device
78from seahub.utils.repo import get_repo_owner, get_library_storages, \
79        get_locked_files_by_dir, get_related_users_by_repo, \
80        is_valid_repo_id_format, can_set_folder_perm_by_user, \
81        add_encrypted_repo_secret_key_to_database, get_available_repo_perms, \
82        parse_repo_perm
83from seahub.utils.star import star_file, unstar_file, get_dir_starred_files
84from seahub.utils.file_tags import get_files_tags_in_dir
85from seahub.utils.file_types import DOCUMENT, MARKDOWN
86from seahub.utils.file_size import get_file_size_unit
87from seahub.utils.file_op import check_file_lock
88from seahub.utils.timeutils import utc_to_local, \
89        datetime_to_isoformat_timestr, datetime_to_timestamp, \
90        timestamp_to_isoformat_timestr
91from seahub.views import is_registered_user, check_folder_permission, \
92    create_default_library, list_inner_pub_repos
93from seahub.views.file import get_file_view_path_and_perm, send_file_access_msg, can_edit_file
94if HAS_FILE_SEARCH:
95    from seahub_extra.search.utils import search_files, get_search_repos_map, SEARCH_FILEEXT
96from seahub.utils import HAS_OFFICE_CONVERTER
97if HAS_OFFICE_CONVERTER:
98    from seahub.utils import query_office_convert_status, prepare_converted_html
99import seahub.settings as settings
100from seahub.settings import THUMBNAIL_EXTENSION, THUMBNAIL_ROOT, \
101    FILE_LOCK_EXPIRATION_DAYS, ENABLE_STORAGE_CLASSES, \
102    ENABLE_THUMBNAIL, STORAGE_CLASS_MAPPING_POLICY, \
103    ENABLE_RESET_ENCRYPTED_REPO_PASSWORD, SHARE_LINK_EXPIRE_DAYS_MAX, \
104        SHARE_LINK_EXPIRE_DAYS_MIN, SHARE_LINK_EXPIRE_DAYS_DEFAULT
105
106
107try:
108    from seahub.settings import CLOUD_MODE
109except ImportError:
110    CLOUD_MODE = False
111try:
112    from seahub.settings import MULTI_TENANCY
113except ImportError:
114    MULTI_TENANCY = False
115try:
116    from seahub.settings import ORG_MEMBER_QUOTA_DEFAULT
117except ImportError:
118    ORG_MEMBER_QUOTA_DEFAULT = None
119
120try:
121    from seahub.settings import ENABLE_OFFICE_WEB_APP
122except ImportError:
123    ENABLE_OFFICE_WEB_APP = False
124
125try:
126    from seahub.settings import OFFICE_WEB_APP_FILE_EXTENSION
127except ImportError:
128    OFFICE_WEB_APP_FILE_EXTENSION = ()
129
130from pysearpc import SearpcError, SearpcObjEncoder
131import seaserv
132from seaserv import seafserv_threaded_rpc, \
133    is_personal_repo, get_repo, check_permission, get_commits,\
134    check_quota, list_share_repos, get_group_repos_by_owner, get_group_repoids, \
135    remove_share, get_group, get_file_id_by_path, edit_repo, \
136    ccnet_threaded_rpc, get_personal_groups, seafile_api, \
137    create_org, ccnet_api
138
139from constance import config
140
141logger = logging.getLogger(__name__)
142json_content_type = 'application/json; charset=utf-8'
143
144# Define custom HTTP status code. 4xx starts from 440, 5xx starts from 520.
145HTTP_440_REPO_PASSWD_REQUIRED = 440
146HTTP_441_REPO_PASSWD_MAGIC_REQUIRED = 441
147HTTP_443_ABOVE_QUOTA = 443
148HTTP_520_OPERATION_FAILED = 520
149
150########## Test
151class Ping(APIView):
152    """
153    Returns a simple `pong` message when client calls `api2/ping/`.
154    For example:
155        curl http://127.0.0.1:8000/api2/ping/
156    """
157    throttle_classes = (ScopedRateThrottle, )
158    throttle_scope = 'ping'
159
160    def get(self, request, format=None):
161        return Response('pong')
162
163    def head(self, request, format=None):
164        return Response(headers={'foo': 'bar',})
165
166class AuthPing(APIView):
167    """
168    Returns a simple `pong` message when client provided an auth token.
169    For example:
170        curl -H "Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" http://127.0.0.1:8000/api2/auth/ping/
171    """
172    authentication_classes = (TokenAuthentication, )
173    permission_classes = (IsAuthenticated,)
174    throttle_classes = (UserRateThrottle, )
175
176    def get(self, request, format=None):
177        return Response('pong')
178
179########## Token
180class ObtainAuthToken(APIView):
181    """
182    Returns auth token if username and password are valid.
183    For example:
184        curl -d "username=foo@example.com&password=123456" http://127.0.0.1:8000/api2/auth-token/
185    """
186    throttle_classes = (AnonRateThrottle, )
187    permission_classes = ()
188    parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
189    renderer_classes = (renderers.JSONRenderer,)
190
191    def post(self, request):
192        headers = {}
193        context = { 'request': request }
194        serializer = AuthTokenSerializer(data=request.data, context=context)
195        if serializer.is_valid():
196            key = serializer.validated_data
197
198            trust_dev = False
199            try:
200                trust_dev_header = int(request.META.get('HTTP_X_SEAFILE_2FA_TRUST_DEVICE', ''))
201                trust_dev = True if trust_dev_header == 1 else False
202            except ValueError:
203                trust_dev = False
204
205            skip_2fa_header = request.META.get('HTTP_X_SEAFILE_S2FA', None)
206            if skip_2fa_header is None:
207                if trust_dev:
208                    # 2fa login with trust device,
209                    # create new session, and return session id.
210                    pass
211                else:
212                    # No 2fa login or 2fa login without trust device,
213                    # return token only.
214                    return Response({'token': key})
215            else:
216                # 2fa login without OTP token,
217                # get or create session, and return session id
218                pass
219
220            SessionStore = import_module(dj_settings.SESSION_ENGINE).SessionStore
221            s = SessionStore(skip_2fa_header)
222            if not s.exists(skip_2fa_header) or s.is_empty():
223                from seahub.two_factor.views.login import remember_device
224                s = remember_device(request.data['username'])
225
226            headers = {
227                'X-SEAFILE-S2FA': s.session_key
228            }
229            return Response({'token': key}, headers=headers)
230
231        if serializer.two_factor_auth_failed:
232            # Add a special response header so the client knows to ask the user
233            # for the 2fa token.
234            headers = {
235                'X-Seafile-OTP': 'required',
236            }
237
238        return Response(serializer.errors,
239                        status=status.HTTP_400_BAD_REQUEST,
240                        headers=headers)
241
242########## Accounts
243class Accounts(APIView):
244    """List all accounts.
245    Administrator permission is required.
246    """
247    authentication_classes = (TokenAuthentication, )
248    permission_classes = (IsAdminUser, )
249    throttle_classes = (UserRateThrottle, )
250
251    def get(self, request, format=None):
252        # list accounts
253        start = int(request.GET.get('start', '0'))
254        limit = int(request.GET.get('limit', '100'))
255        # reading scope user list
256        scope = request.GET.get('scope', None)
257
258        accounts_ldapimport = []
259        accounts_ldap = []
260        accounts_db = []
261        if scope:
262            scope = scope.upper()
263            if scope == 'LDAP':
264                accounts_ldap = ccnet_api.get_emailusers('LDAP', start, limit)
265            elif scope == 'LDAPIMPORT':
266                accounts_ldapimport = ccnet_api.get_emailusers('LDAPImport', start, limit)
267            elif scope == 'DB':
268                accounts_db = ccnet_api.get_emailusers('DB', start, limit)
269            else:
270                return api_error(status.HTTP_400_BAD_REQUEST, "%s is not a valid scope value" % scope)
271        else:
272            # old way - search first in LDAP if available then DB if no one found
273            accounts_ldap = seaserv.get_emailusers('LDAP', start, limit)
274            if len(accounts_ldap) == 0:
275                accounts_db = seaserv.get_emailusers('DB', start, limit)
276
277        accounts_json = []
278        for account in accounts_ldap:
279            accounts_json.append({'email': account.email, 'source' : 'LDAP'})
280
281        for account in accounts_ldapimport:
282            accounts_json.append({'email': account.email, 'source' : 'LDAPImport'})
283
284        for account in accounts_db:
285            accounts_json.append({'email': account.email, 'source' : 'DB'})
286
287        return Response(accounts_json)
288
289
290class AccountInfo(APIView):
291    """ Show account info.
292    """
293    authentication_classes = (TokenAuthentication, SessionAuthentication)
294    permission_classes = (IsAuthenticated,)
295    throttle_classes = (UserRateThrottle, )
296
297    def _get_account_info(self, request):
298        info = {}
299        email = request.user.username
300        p = Profile.objects.get_profile_by_user(email)
301        d_p = DetailedProfile.objects.get_detailed_profile_by_user(email)
302
303        if is_org_context(request):
304            org_id = request.user.org.org_id
305            quota_total = seafile_api.get_org_user_quota(org_id, email)
306            quota_usage = seafile_api.get_org_user_quota_usage(org_id, email)
307            is_org_staff = request.user.org.is_staff
308            info['is_org_staff'] = is_org_staff
309        else:
310            quota_total = seafile_api.get_user_quota(email)
311            quota_usage = seafile_api.get_user_self_usage(email)
312
313        if quota_total > 0:
314            info['space_usage'] = str(float(quota_usage) / quota_total * 100) + '%'
315        else:                       # no space quota set in config
316            info['space_usage'] = '0%'
317
318        url, _, _ = api_avatar_url(email, int(72))
319
320        info['avatar_url'] = url
321        info['email'] = email
322        info['name'] = email2nickname(email)
323        info['total'] = quota_total
324        info['usage'] = quota_usage
325        info['login_id'] = p.login_id if p and p.login_id else ""
326        info['department'] = d_p.department if d_p else ""
327        info['contact_email'] = p.contact_email if p else ""
328        info['institution'] = p.institution if p and p.institution else ""
329        info['is_staff'] = request.user.is_staff
330
331        if getattr(settings, 'MULTI_INSTITUTION', False):
332            from seahub.institutions.models import InstitutionAdmin
333            try:
334                InstitutionAdmin.objects.get(user=email)
335                info['is_inst_admin'] = True
336            except InstitutionAdmin.DoesNotExist:
337                info['is_inst_admin'] = False
338
339        file_updates_email_interval = UserOptions.objects.get_file_updates_email_interval(email)
340        info['file_updates_email_interval'] = 0 if file_updates_email_interval is None else file_updates_email_interval
341        collaborate_email_interval = UserOptions.objects.get_collaborate_email_interval(email)
342        info['collaborate_email_interval'] = 0 if collaborate_email_interval is None else collaborate_email_interval
343        return info
344
345    def get(self, request, format=None):
346        return Response(self._get_account_info(request))
347
348    def put(self, request, format=None):
349        """Update account info.
350        """
351        username = request.user.username
352
353        name = request.data.get("name", None)
354        if name is not None:
355            if len(name) > 64:
356                return api_error(status.HTTP_400_BAD_REQUEST,
357                        _('Name is too long (maximum is 64 characters)'))
358
359            if "/" in name:
360                return api_error(status.HTTP_400_BAD_REQUEST,
361                        _("Name should not include '/'."))
362
363        file_updates_email_interval = request.data.get("file_updates_email_interval", None)
364        if file_updates_email_interval is not None:
365            try:
366                file_updates_email_interval = int(file_updates_email_interval)
367            except ValueError:
368                return api_error(status.HTTP_400_BAD_REQUEST,
369                                 'file_updates_email_interval invalid')
370        collaborate_email_interval = request.data.get("collaborate_email_interval", None)
371        if collaborate_email_interval is not None:
372            try:
373                collaborate_email_interval = int(collaborate_email_interval)
374            except ValueError:
375                return api_error(status.HTTP_400_BAD_REQUEST,
376                                 'collaborate_email_interval invalid')
377
378        # update user info
379
380        if name is not None:
381            profile = Profile.objects.get_profile_by_user(username)
382            if profile is None:
383                profile = Profile(user=username)
384            profile.nickname = name
385            profile.save()
386
387        if file_updates_email_interval is not None:
388            if file_updates_email_interval <= 0:
389                UserOptions.objects.unset_file_updates_email_interval(username)
390            else:
391                UserOptions.objects.set_file_updates_email_interval(
392                    username, file_updates_email_interval)
393
394        if collaborate_email_interval is not None:
395            UserOptions.objects.set_collaborate_email_interval(
396                username, collaborate_email_interval)
397
398        return Response(self._get_account_info(request))
399
400
401class RegDevice(APIView):
402    """Reg device for iOS push notification.
403    """
404    authentication_classes = (TokenAuthentication, )
405    permission_classes = (IsAuthenticated,)
406    throttle_classes = (UserRateThrottle, )
407
408    def post(self, request, format=None):
409        version = request.POST.get('version')
410        platform = request.POST.get('platform')
411        pversion = request.POST.get('pversion')
412        devicetoken = request.POST.get('deviceToken')
413        if not devicetoken or not version or not platform or not pversion:
414            return api_error(status.HTTP_400_BAD_REQUEST, "Missing argument")
415
416        token, modified = DeviceToken.objects.get_or_create(
417            token=devicetoken, user=request.user.username)
418        if token.version != version:
419            token.version = version
420            modified = True
421        if token.pversion != pversion:
422            token.pversion = pversion
423            modified = True
424        if token.platform != platform:
425            token.platform = platform
426            modified = True
427
428        if modified:
429            token.save()
430        return Response("success")
431
432
433class Search(APIView):
434    """ Search all the repos
435    """
436    authentication_classes = (TokenAuthentication, SessionAuthentication)
437    permission_classes = (IsAuthenticated,)
438    throttle_classes = (UserRateThrottle, )
439
440    def get(self, request, format=None):
441
442        if not HAS_FILE_SEARCH:
443            error_msg = 'Search not supported.'
444            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
445
446        # argument check
447        keyword = request.GET.get('q', None)
448        if not keyword:
449            error_msg = 'q invalid.'
450            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
451
452        try:
453            current_page = int(request.GET.get('page', '1'))
454            per_page = int(request.GET.get('per_page', '10'))
455            if per_page > 100:
456                per_page = 100
457        except ValueError:
458            current_page = 1
459            per_page = 10
460
461        start = (current_page - 1) * per_page
462        size = per_page
463        if start < 0 or size < 0:
464            error_msg = 'page or per_page invalid.'
465            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
466
467        search_repo = request.GET.get('search_repo', 'all') # val: scope or 'repo_id'
468        search_repo = search_repo.lower()
469        if not is_valid_repo_id_format(search_repo) and \
470                search_repo not in ('all', 'mine', 'shared', 'group', 'public'):
471            error_msg = 'search_repo invalid.'
472            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
473
474        search_path = request.GET.get('search_path', None)
475        if search_path:
476            search_path = normalize_dir_path(search_path)
477            if not is_valid_repo_id_format(search_repo):
478                error_msg = 'search_repo invalid.'
479                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
480
481            dir_id = seafile_api.get_dir_id_by_path(search_repo, search_path)
482            if not dir_id:
483                error_msg = 'Folder %s not found.' % search_path
484                return api_error(status.HTTP_404_NOT_FOUND, error_msg)
485
486        obj_type = request.GET.get('obj_type', None)
487        if obj_type:
488            obj_type = obj_type.lower()
489
490        if obj_type and obj_type not in ('dir', 'file'):
491            error_msg = 'obj_type invalid.'
492            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
493
494        search_ftypes = request.GET.get('search_ftypes', 'all') # val: 'all' or 'custom'
495        search_ftypes = search_ftypes.lower()
496        if search_ftypes not in ('all', 'custom'):
497            error_msg = 'search_ftypes invalid.'
498            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
499
500        with_permission = request.GET.get('with_permission', 'false')
501        with_permission = with_permission.lower()
502        if with_permission not in ('true', 'false'):
503            error_msg = 'with_permission invalid.'
504            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
505
506        time_from = request.GET.get('time_from', None)
507        time_to = request.GET.get('time_to', None)
508        if time_from is not None:
509            try:
510                time_from = int(time_from)
511            except:
512                error_msg = 'time_from invalid.'
513                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
514
515        if time_to is not None:
516            try:
517                time_to = int(time_to)
518            except:
519                error_msg = 'time_to invalid.'
520                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
521
522        size_from = request.GET.get('size_from', None)
523        size_to = request.GET.get('size_to', None)
524        if size_from is not None:
525            try:
526                size_from = int(size_from)
527            except:
528                error_msg = 'size_from invalid.'
529                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
530
531        if size_to is not None:
532            try:
533                size_to = int(size_to)
534            except:
535                error_msg = 'size_to invalid.'
536                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
537
538        time_range = (time_from, time_to)
539        size_range = (size_from, size_to)
540
541        suffixes = None
542        custom_ftypes =  request.GET.getlist('ftype') # types like 'Image', 'Video'... same in utils/file_types.py
543        input_fileexts = request.GET.get('input_fexts', '') # file extension input by the user
544        if search_ftypes == 'custom':
545            suffixes = []
546            if len(custom_ftypes) > 0:
547                for ftp in custom_ftypes:
548                    if ftp in SEARCH_FILEEXT:
549                        for ext in SEARCH_FILEEXT[ftp]:
550                            suffixes.append(ext)
551
552            if input_fileexts:
553                input_fexts = input_fileexts.split(',')
554                for i_ext in input_fexts:
555                    i_ext = i_ext.strip()
556                    if i_ext:
557                        suffixes.append(i_ext)
558
559        username = request.user.username
560        org_id = request.user.org.org_id if is_org_context(request) else None
561        repo_id_map = {}
562        # check recourse and permissin when search in a single repo
563        if is_valid_repo_id_format(search_repo):
564            repo_id = search_repo
565            repo = seafile_api.get_repo(repo_id)
566            # recourse check
567            if not repo:
568                error_msg = 'Library %s not found.' % repo_id
569                return api_error(status.HTTP_404_NOT_FOUND, error_msg)
570
571            # permission check
572            if not check_folder_permission(request, repo_id, '/'):
573                error_msg = 'Permission denied.'
574                return api_error(status.HTTP_403_FORBIDDEN, error_msg)
575            map_id = repo.origin_repo_id if repo.origin_repo_id else repo_id
576            repo_id_map[map_id] = repo
577            repo_type_map = {}
578        else:
579            shared_from = request.GET.get('shared_from', None)
580            not_shared_from = request.GET.get('not_shared_from', None)
581            repo_id_map, repo_type_map = get_search_repos_map(search_repo,
582                    username, org_id, shared_from, not_shared_from)
583
584        obj_desc = {
585            'obj_type': obj_type,
586            'suffixes': suffixes,
587            'time_range': time_range,
588            'size_range': size_range
589        }
590        # search file
591        try:
592            results, total = search_files(repo_id_map, search_path, keyword, obj_desc, start, size, org_id)
593        except Exception as e:
594            logger.error(e)
595            error_msg = 'Internal Server Error'
596            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
597
598        for e in results:
599            e.pop('repo', None)
600            e.pop('exists', None)
601            e.pop('last_modified_by', None)
602            e.pop('name_highlight', None)
603            e.pop('score', None)
604
605            repo_id = e['repo_id']
606
607            if with_permission.lower() == 'true':
608                permission = check_folder_permission(request, repo_id, '/')
609                if not permission:
610                    continue
611                e['permission'] = permission
612
613            # get repo type
614            if repo_id in repo_type_map:
615                e['repo_type'] = repo_type_map[repo_id]
616            else:
617                e['repo_type'] = ''
618
619            e['thumbnail_url'] = ''
620            filetype, fileext = get_file_type_and_ext(e.get('name', ''))
621
622            if filetype == IMAGE:
623                thumbnail_url = reverse('api2-thumbnail',
624                                        args=[e.get('repo_id', '')],
625                                        request=request)
626                params = '?p={}&size={}'.format(quote(e.get('fullpath', '').encode('utf-8')), 72)
627                e['thumbnail_url'] = thumbnail_url + params
628
629        has_more = True if total > current_page * per_page else False
630        return Response({"total":total, "results":results, "has_more":has_more})
631
632########## Repo related
633def repo_download_info(request, repo_id, gen_sync_token=True):
634    repo = get_repo(repo_id)
635    if not repo:
636        return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
637
638    # generate download url for client
639    email = request.user.username
640    if gen_sync_token:
641        token = seafile_api.generate_repo_token(repo_id, email)
642    else:
643        token = ''
644    repo_name = repo.name
645    repo_desc = repo.desc
646    repo_size = repo.size
647    repo_size_formatted = filesizeformat(repo.size)
648    enc = 1 if repo.encrypted else ''
649    magic = repo.magic if repo.encrypted else ''
650    random_key = repo.random_key if repo.random_key else ''
651    enc_version = repo.enc_version
652    repo_version = repo.version
653
654    calculate_repos_last_modify([repo])
655
656    info_json = {
657        'relay_id': '44e8f253849ad910dc142247227c8ece8ec0f971',
658        'relay_addr': '127.0.0.1',
659        'relay_port': '80',
660        'email': email,
661        'token': token,
662        'repo_id': repo_id,
663        'repo_name': repo_name,
664        'repo_desc': repo_desc,
665        'repo_size': repo_size,
666        'repo_size_formatted': repo_size_formatted,
667        'mtime': repo.latest_modify,
668        'mtime_relative': translate_seahub_time(repo.latest_modify),
669        'encrypted': enc,
670        'enc_version': enc_version,
671        'salt': repo.salt if enc_version >= 3 else '',
672        'magic': magic,
673        'random_key': random_key,
674        'repo_version': repo_version,
675        'head_commit_id': repo.head_cmmt_id,
676        'permission': seafile_api.check_permission_by_path(repo_id, '/', email)
677        }
678
679    if is_pro_version() and ENABLE_STORAGE_CLASSES:
680        info_json['storage_name'] = repo.storage_name
681
682    return Response(info_json)
683
684class Repos(APIView):
685    authentication_classes = (TokenAuthentication, SessionAuthentication)
686    permission_classes = (IsAuthenticated,)
687    throttle_classes = (UserRateThrottle, )
688
689    def get(self, request, format=None):
690        # parse request params
691        filter_by = {
692            'mine': False,
693            'shared': False,
694            'group': False,
695            'org': False,
696        }
697
698        q = request.GET.get('nameContains', '')
699        rtype = request.GET.get('type', "")
700        if not rtype:
701            # set all to True, no filter applied
702            filter_by = filter_by.fromkeys(iter(filter_by.keys()), True)
703
704        for f in rtype.split(','):
705            f = f.strip()
706            filter_by[f] = True
707
708        email = request.user.username
709        owner_name = email2nickname(email)
710        owner_contact_email = email2contact_email(email)
711
712        # Use dict to reduce memcache fetch cost in large for-loop.
713        contact_email_dict = {}
714        nickname_dict = {}
715
716        repos_json = []
717        if filter_by['mine']:
718            if is_org_context(request):
719                org_id = request.user.org.org_id
720                owned_repos = seafile_api.get_org_owned_repo_list(org_id,
721                        email, ret_corrupted=True)
722            else:
723                owned_repos = seafile_api.get_owned_repo_list(email,
724                        ret_corrupted=True)
725
726            # Reduce memcache fetch ops.
727            modifiers_set = {x.last_modifier for x in owned_repos}
728            for e in modifiers_set:
729                if e not in contact_email_dict:
730                    contact_email_dict[e] = email2contact_email(e)
731                if e not in nickname_dict:
732                    nickname_dict[e] = email2nickname(e)
733
734            owned_repos.sort(key=lambda x: x.last_modify, reverse=True)
735            for r in owned_repos:
736                # do not return virtual repos
737                if r.is_virtual:
738                    continue
739
740                if q and q.lower() not in r.name.lower():
741                    continue
742
743                repo = {
744                    "type": "repo",
745                    "id": r.id,
746                    "owner": email,
747                    "owner_name": owner_name,
748                    "owner_contact_email": owner_contact_email,
749                    "name": r.name,
750                    "mtime": r.last_modify,
751                    "modifier_email": r.last_modifier,
752                    "modifier_contact_email": contact_email_dict.get(r.last_modifier, ''),
753                    "modifier_name": nickname_dict.get(r.last_modifier, ''),
754                    "mtime_relative": translate_seahub_time(r.last_modify),
755                    "size": r.size,
756                    "size_formatted": filesizeformat(r.size),
757                    "encrypted": r.encrypted,
758                    "permission": 'rw',  # Always have read-write permission to owned repo
759                    "virtual": False,
760                    "root": '',
761                    "head_commit_id": r.head_cmmt_id,
762                    "version": r.version,
763                    "salt": r.salt if r.enc_version >= 3 else '',
764                }
765
766                if is_pro_version() and ENABLE_STORAGE_CLASSES:
767                    repo['storage_name'] = r.storage_name
768                    repo['storage_id'] = r.storage_id
769
770                repos_json.append(repo)
771
772        if filter_by['shared']:
773
774            if is_org_context(request):
775                org_id = request.user.org.org_id
776                shared_repos = seafile_api.get_org_share_in_repo_list(org_id,
777                        email, -1, -1)
778            else:
779                shared_repos = seafile_api.get_share_in_repo_list(
780                        email, -1, -1)
781
782            repos_with_admin_share_to = ExtraSharePermission.objects.\
783                    get_repos_with_admin_permission(email)
784
785            # Reduce memcache fetch ops.
786            owners_set = {x.user for x in shared_repos}
787            modifiers_set = {x.last_modifier for x in shared_repos}
788            for e in owners_set | modifiers_set:
789                if e not in contact_email_dict:
790                    contact_email_dict[e] = email2contact_email(e)
791                if e not in nickname_dict:
792                    nickname_dict[e] = email2nickname(e)
793
794            shared_repos.sort(key=lambda x: x.last_modify, reverse=True)
795            for r in shared_repos:
796                if q and q.lower() not in r.name.lower():
797                    continue
798
799                library_group_name = ''
800                if '@seafile_group' in r.user:
801                    library_group_id = get_group_id_by_repo_owner(r.user)
802                    library_group_name= group_id_to_name(library_group_id)
803
804                if parse_repo_perm(r.permission).can_download is False:
805                    if not is_web_request(request):
806                        continue
807
808                r.password_need = seafile_api.is_password_set(r.repo_id, email)
809                repo = {
810                    "type": "srepo",
811                    "id": r.repo_id,
812                    "owner": r.user,
813                    "owner_name": nickname_dict.get(r.user, ''),
814                    "owner_contact_email": contact_email_dict.get(r.user, ''),
815                    "name": r.repo_name,
816                    "owner_nickname": nickname_dict.get(r.user, ''),
817                    "mtime": r.last_modify,
818                    "mtime_relative": translate_seahub_time(r.last_modify),
819                    "modifier_email": r.last_modifier,
820                    "modifier_contact_email": contact_email_dict.get(r.last_modifier, ''),
821                    "modifier_name": nickname_dict.get(r.last_modifier, ''),
822                    "size": r.size,
823                    "size_formatted": filesizeformat(r.size),
824                    "encrypted": r.encrypted,
825                    "permission": r.permission,
826                    "share_type": r.share_type,
827                    "root": '',
828                    "head_commit_id": r.head_cmmt_id,
829                    "version": r.version,
830                    "group_name": library_group_name,
831                    "salt": r.salt if r.enc_version >= 3 else '',
832                }
833
834                if r.repo_id in repos_with_admin_share_to:
835                    repo['is_admin'] = True
836                else:
837                    repo['is_admin'] = False
838
839                repos_json.append(repo)
840
841        if filter_by['group']:
842            if is_org_context(request):
843                org_id = request.user.org.org_id
844                group_repos = seafile_api.get_org_group_repos_by_user(email,
845                        org_id)
846            else:
847                group_repos = seafile_api.get_group_repos_by_user(email)
848
849            group_repos.sort(key=lambda x: x.last_modify, reverse=True)
850
851            # Reduce memcache fetch ops.
852            share_from_set = {x.user for x in group_repos}
853            modifiers_set = {x.last_modifier for x in group_repos}
854            for e in modifiers_set | share_from_set:
855                if e not in contact_email_dict:
856                    contact_email_dict[e] = email2contact_email(e)
857                if e not in nickname_dict:
858                    nickname_dict[e] = email2nickname(e)
859
860            for r in group_repos:
861                if q and q.lower() not in r.name.lower():
862                    continue
863
864                if parse_repo_perm(r.permission).can_download is False:
865                    if not is_web_request(request):
866                        continue
867
868                repo = {
869                    "type": "grepo",
870                    "id": r.repo_id,
871                    "name": r.repo_name,
872                    "groupid": r.group_id,
873                    "group_name": r.group_name,
874                    "owner": r.group_name,
875                    "mtime": r.last_modify,
876                    "mtime_relative": translate_seahub_time(r.last_modify),
877                    "modifier_email": r.last_modifier,
878                    "modifier_name": nickname_dict.get(r.last_modifier, ''),
879                    "modifier_contact_email": contact_email_dict.get(r.last_modifier, ''),
880                    "size": r.size,
881                    "encrypted": r.encrypted,
882                    "permission": r.permission,
883                    "root": '',
884                    "head_commit_id": r.head_cmmt_id,
885                    "version": r.version,
886                    "share_from": r.user,
887                    "share_from_name": nickname_dict.get(r.user, ''),
888                    "share_from_contact_email": contact_email_dict.get(r.user, ''),
889                    "salt": r.salt if r.enc_version >= 3 else '',
890                }
891                repos_json.append(repo)
892
893        if filter_by['org'] and request.user.permissions.can_view_org():
894            public_repos = list_inner_pub_repos(request)
895
896            # Reduce memcache fetch ops.
897            share_from_set = {x.user for x in public_repos}
898            modifiers_set = {x.last_modifier for x in public_repos}
899            for e in modifiers_set | share_from_set:
900                if e not in contact_email_dict:
901                    contact_email_dict[e] = email2contact_email(e)
902                if e not in nickname_dict:
903                    nickname_dict[e] = email2nickname(e)
904
905            for r in public_repos:
906                if q and q.lower() not in r.name.lower():
907                    continue
908
909                repo = {
910                    "type": "grepo",
911                    "id": r.repo_id,
912                    "name": r.repo_name,
913                    "owner": "Organization",
914                    "mtime": r.last_modified,
915                    "mtime_relative": translate_seahub_time(r.last_modified),
916                    "modifier_email": r.last_modifier,
917                    "modifier_contact_email": contact_email_dict.get(r.last_modifier, ''),
918                    "modifier_name": nickname_dict.get(r.last_modifier, ''),
919                    "size": r.size,
920                    "size_formatted": filesizeformat(r.size),
921                    "encrypted": r.encrypted,
922                    "permission": r.permission,
923                    "share_from": r.user,
924                    "share_from_name": nickname_dict.get(r.user, ''),
925                    "share_from_contact_email": contact_email_dict.get(r.user, ''),
926                    "share_type": r.share_type,
927                    "root": '',
928                    "head_commit_id": r.head_cmmt_id,
929                    "version": r.version,
930                    "salt": r.salt if r.enc_version >= 3 else '',
931                }
932                repos_json.append(repo)
933
934        utc_dt = datetime.datetime.utcnow()
935        timestamp = utc_dt.strftime('%Y-%m-%d %H:%M:%S')
936        org_id = -1
937        if is_org_context(request):
938            org_id = request.user.org.org_id
939
940        try:
941            seafile_api.publish_event('seahub.stats', 'user-login\t%s\t%s\t%s' % (email, timestamp, org_id))
942        except Exception as e:
943            logger.error('Error when sending user-login message: %s' % str(e))
944        response = HttpResponse(json.dumps(repos_json), status=200,
945                                content_type=json_content_type)
946        response["enable_encrypted_library"] = config.ENABLE_ENCRYPTED_LIBRARY
947        return response
948
949    def post(self, request, format=None):
950
951        if not request.user.permissions.can_add_repo():
952            return api_error(status.HTTP_403_FORBIDDEN,
953                             'You do not have permission to create library.')
954
955        req_from = request.GET.get('from', "")
956        if req_from == 'web':
957            gen_sync_token = False  # Do not generate repo sync token
958        else:
959            gen_sync_token = True
960
961        username = request.user.username
962        repo_name = request.data.get("name", None)
963        if not repo_name:
964            return api_error(status.HTTP_400_BAD_REQUEST,
965                             'Library name is required.')
966
967        if not is_valid_dirent_name(repo_name):
968            return api_error(status.HTTP_400_BAD_REQUEST,
969                             'name invalid.')
970
971        repo_desc = request.data.get("desc", '')
972        org_id = -1
973        if is_org_context(request):
974            org_id = request.user.org.org_id
975
976        repo_id = request.data.get('repo_id', '')
977        try:
978            if repo_id:
979                # client generates magic and random key
980                repo_id, error = self._create_enc_repo(request, repo_id, repo_name, repo_desc, username, org_id)
981            else:
982                repo_id, error = self._create_repo(request, repo_name, repo_desc, username, org_id)
983        except SearpcError as e:
984            logger.error(e)
985            return api_error(HTTP_520_OPERATION_FAILED,
986                             'Failed to create library.')
987        if error is not None:
988            return error
989        if not repo_id:
990            return api_error(HTTP_520_OPERATION_FAILED,
991                             'Failed to create library.')
992        else:
993            library_template = request.data.get("library_template", '')
994            repo_created.send(sender=None,
995                              org_id=org_id,
996                              creator=username,
997                              repo_id=repo_id,
998                              repo_name=repo_name,
999                              library_template=library_template)
1000            resp = repo_download_info(request, repo_id,
1001                                      gen_sync_token=gen_sync_token)
1002
1003            # FIXME: according to the HTTP spec, need to return 201 code and
1004            # with a corresponding location header
1005            # resp['Location'] = reverse('api2-repo', args=[repo_id])
1006            return resp
1007
1008    def _create_repo(self, request, repo_name, repo_desc, username, org_id):
1009        passwd = request.data.get("passwd", None)
1010
1011        # to avoid 'Bad magic' error when create repo, passwd should be 'None'
1012        # not an empty string when create unencrypted repo
1013        if not passwd:
1014            passwd = None
1015
1016        if (passwd is not None) and (not config.ENABLE_ENCRYPTED_LIBRARY):
1017            return None, api_error(status.HTTP_403_FORBIDDEN,
1018                             'NOT allow to create encrypted library.')
1019
1020        if org_id and org_id > 0:
1021            repo_id = seafile_api.create_org_repo(repo_name,
1022                    repo_desc, username, org_id, passwd,
1023                    enc_version=settings.ENCRYPTED_LIBRARY_VERSION)
1024        else:
1025            if is_pro_version() and ENABLE_STORAGE_CLASSES:
1026
1027                if STORAGE_CLASS_MAPPING_POLICY in ('USER_SELECT',
1028                        'ROLE_BASED'):
1029
1030                    storages = get_library_storages(request)
1031                    storage_id = request.data.get("storage_id", None)
1032                    if storage_id and storage_id not in [s['storage_id'] for s in storages]:
1033                        error_msg = 'storage_id invalid.'
1034                        return None, api_error(status.HTTP_400_BAD_REQUEST, error_msg)
1035
1036                    repo_id = seafile_api.create_repo(repo_name,
1037                            repo_desc, username, passwd,
1038                            enc_version=settings.ENCRYPTED_LIBRARY_VERSION,
1039                            storage_id=storage_id )
1040                else:
1041                    # STORAGE_CLASS_MAPPING_POLICY == 'REPO_ID_MAPPING'
1042                    repo_id = seafile_api.create_repo(repo_name,
1043                            repo_desc, username, passwd,
1044                            enc_version=settings.ENCRYPTED_LIBRARY_VERSION)
1045            else:
1046                repo_id = seafile_api.create_repo(repo_name,
1047                        repo_desc, username, passwd,
1048                        enc_version=settings.ENCRYPTED_LIBRARY_VERSION)
1049
1050        if passwd and ENABLE_RESET_ENCRYPTED_REPO_PASSWORD:
1051            add_encrypted_repo_secret_key_to_database(repo_id, passwd)
1052
1053        return repo_id, None
1054
1055    def _create_enc_repo(self, request, repo_id, repo_name, repo_desc, username, org_id):
1056        if not config.ENABLE_ENCRYPTED_LIBRARY:
1057            return None, api_error(status.HTTP_403_FORBIDDEN, 'NOT allow to create encrypted library.')
1058        if not _REPO_ID_PATTERN.match(repo_id):
1059            return None, api_error(status.HTTP_400_BAD_REQUEST, 'Repo id must be a valid uuid')
1060        magic = request.data.get('magic', '')
1061        random_key = request.data.get('random_key', '')
1062
1063        try:
1064            enc_version = int(request.data.get('enc_version', 0))
1065        except ValueError:
1066            return None, api_error(status.HTTP_400_BAD_REQUEST,
1067                             'Invalid enc_version param.')
1068
1069        if enc_version > settings.ENCRYPTED_LIBRARY_VERSION:
1070            return None, api_error(status.HTTP_400_BAD_REQUEST,
1071                             'Invalid enc_version param.')
1072
1073        salt = None
1074        if enc_version >= 3 and settings.ENCRYPTED_LIBRARY_VERSION >= 3:
1075            salt = request.data.get('salt', '')
1076            if not salt:
1077                error_msg = 'salt invalid.'
1078                return None, api_error(status.HTTP_400_BAD_REQUEST, error_msg)
1079
1080        if len(magic) != 64 or len(random_key) != 96 or enc_version < 0:
1081            return None, api_error(status.HTTP_400_BAD_REQUEST,
1082                             'You must provide magic, random_key and enc_version.')
1083
1084        if org_id and org_id > 0:
1085            repo_id = seafile_api.create_org_enc_repo(repo_id, repo_name, repo_desc,
1086                                                      username, magic, random_key,
1087                                                      salt, enc_version, org_id)
1088        else:
1089            if is_pro_version() and ENABLE_STORAGE_CLASSES and \
1090                    STORAGE_CLASS_MAPPING_POLICY == 'ROLE_BASED':
1091
1092                storages = get_library_storages(request)
1093
1094                if not storages:
1095                    logger.error('no library storage found.')
1096                    repo_id = seafile_api.create_enc_repo(repo_id, repo_name, \
1097                            repo_desc, username, magic, random_key, \
1098                            salt, enc_version)
1099                else:
1100                    repo_id = seafile_api.create_enc_repo(repo_id, repo_name, \
1101                            repo_desc, username, magic, random_key, \
1102                            salt, enc_version, \
1103                            storage_id=storages[0].get('storage_id', None))
1104            else:
1105                repo_id = seafile_api.create_enc_repo(repo_id, repo_name, \
1106                        repo_desc, username, magic, random_key, \
1107                        salt, enc_version)
1108
1109        return repo_id, None
1110
1111
1112class PubRepos(APIView):
1113    authentication_classes = (TokenAuthentication, SessionAuthentication)
1114    permission_classes = (IsAuthenticated,)
1115    throttle_classes = (UserRateThrottle, )
1116
1117    def get(self, request, format=None):
1118        # List public repos
1119        if not request.user.permissions.can_view_org():
1120            return api_error(status.HTTP_403_FORBIDDEN,
1121                             'You do not have permission to view public libraries.')
1122
1123        repos_json = []
1124        public_repos = list_inner_pub_repos(request)
1125        for r in public_repos:
1126            repo = {
1127                "id": r.repo_id,
1128                "name": r.repo_name,
1129                "owner": r.user,
1130                "owner_nickname": email2nickname(r.user),
1131                "owner_name": email2nickname(r.user),
1132                "mtime": r.last_modified,
1133                "mtime_relative": translate_seahub_time(r.last_modified),
1134                "size": r.size,
1135                "size_formatted": filesizeformat(r.size),
1136                "encrypted": r.encrypted,
1137                "permission": r.permission,
1138            }
1139            repos_json.append(repo)
1140
1141        return Response(repos_json)
1142
1143    def post(self, request, format=None):
1144        # Create public repo
1145        if not request.user.permissions.can_add_public_repo():
1146            return api_error(status.HTTP_403_FORBIDDEN,
1147                             'You do not have permission to create library.')
1148
1149        username = request.user.username
1150        repo_name = request.data.get("name", None)
1151        if not repo_name:
1152            return api_error(status.HTTP_400_BAD_REQUEST,
1153                             'Library name is required.')
1154        repo_desc = request.data.get("desc", '')
1155        passwd = request.data.get("passwd", None)
1156
1157        # to avoid 'Bad magic' error when create repo, passwd should be 'None'
1158        # not an empty string when create unencrypted repo
1159        if not passwd:
1160            passwd = None
1161
1162        if (passwd is not None) and (not config.ENABLE_ENCRYPTED_LIBRARY):
1163            return api_error(status.HTTP_403_FORBIDDEN,
1164                             'NOT allow to create encrypted library.')
1165
1166        permission = request.data.get("permission", 'r')
1167        if permission != 'r' and permission != 'rw':
1168            return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid permission')
1169
1170        org_id = -1
1171        if is_org_context(request):
1172            org_id = request.user.org.org_id
1173            repo_id = seafile_api.create_org_repo(repo_name, repo_desc,
1174                                                  username, org_id, passwd,
1175                                                  enc_version=settings.ENCRYPTED_LIBRARY_VERSION)
1176            repo = seafile_api.get_repo(repo_id)
1177            seafile_api.set_org_inner_pub_repo(org_id, repo.id, permission)
1178        else:
1179            if is_pro_version() and ENABLE_STORAGE_CLASSES:
1180
1181                if STORAGE_CLASS_MAPPING_POLICY in ('USER_SELECT',
1182                        'ROLE_BASED'):
1183
1184                    storages = get_library_storages(request)
1185                    storage_id = request.data.get("storage_id", None)
1186                    if storage_id and storage_id not in [s['storage_id'] for s in storages]:
1187                        error_msg = 'storage_id invalid.'
1188                        return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
1189
1190                    repo_id = seafile_api.create_repo(repo_name,
1191                            repo_desc, username, passwd,
1192                            enc_version=settings.ENCRYPTED_LIBRARY_VERSION,
1193                            storage_id=storage_id)
1194                else:
1195                    # STORAGE_CLASS_MAPPING_POLICY == 'REPO_ID_MAPPING'
1196                    repo_id = seafile_api.create_repo(repo_name,
1197                            repo_desc, username, passwd,
1198                            enc_version=settings.ENCRYPTED_LIBRARY_VERSION)
1199            else:
1200                repo_id = seafile_api.create_repo(repo_name,
1201                        repo_desc, username, passwd,
1202                        enc_version=settings.ENCRYPTED_LIBRARY_VERSION)
1203
1204            repo = seafile_api.get_repo(repo_id)
1205            seafile_api.add_inner_pub_repo(repo.id, permission)
1206
1207        try:
1208            send_perm_audit_msg('add-repo-perm',
1209                    username, 'all', repo_id, '/', permission)
1210        except Exception as e:
1211            logger.error(e)
1212
1213
1214        library_template = request.data.get("library_template", '')
1215        repo_created.send(sender=None,
1216                          org_id=org_id,
1217                          creator=username,
1218                          repo_id=repo_id,
1219                          repo_name=repo_name,
1220                          library_template=library_template)
1221        pub_repo = {
1222            "id": repo.id,
1223            "name": repo.name,
1224            "desc": repo.desc,
1225            "size": repo.size,
1226            "size_formatted": filesizeformat(repo.size),
1227            "mtime": repo.last_modify,
1228            "mtime_relative": translate_seahub_time(repo.last_modify),
1229            "encrypted": repo.encrypted,
1230            "permission": 'rw',  # Always have read-write permission to owned repo
1231            "owner": username,
1232            "owner_nickname": email2nickname(username),
1233            "owner_name": email2nickname(username),
1234        }
1235
1236        return Response(pub_repo, status=201)
1237
1238def set_repo_password(request, repo, password):
1239    assert password, 'password must not be none'
1240
1241    repo_id = repo.id
1242    try:
1243        seafile_api.set_passwd(repo_id, request.user.username, password)
1244
1245        if ENABLE_RESET_ENCRYPTED_REPO_PASSWORD:
1246            add_encrypted_repo_secret_key_to_database(repo_id, password)
1247
1248    except SearpcError as e:
1249        if e.msg == 'Bad arguments':
1250            return api_error(status.HTTP_400_BAD_REQUEST, e.msg)
1251        elif e.msg == 'Repo is not encrypted':
1252            return api_error(status.HTTP_409_CONFLICT, e.msg)
1253        elif e.msg == 'Incorrect password':
1254            return api_error(status.HTTP_400_BAD_REQUEST, e.msg)
1255        elif e.msg == 'Internal server error':
1256            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, e.msg)
1257        else:
1258            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, e.msg)
1259
1260
1261def check_set_repo_password(request, repo):
1262    if not check_permission(repo.id, request.user.username):
1263        return api_error(status.HTTP_403_FORBIDDEN,
1264                'You do not have permission to access this library.')
1265
1266    if repo.encrypted:
1267        password = request.POST.get('password', default=None)
1268        if not password:
1269            return api_error(HTTP_440_REPO_PASSWD_REQUIRED,
1270                             'Library password is needed.')
1271
1272        return set_repo_password(request, repo, password)
1273
1274
1275class Repo(APIView):
1276    authentication_classes = (TokenAuthentication, SessionAuthentication)
1277    permission_classes = (IsAuthenticated, )
1278    throttle_classes = (UserRateThrottle, )
1279
1280    def get(self, request, repo_id, format=None):
1281        repo = get_repo(repo_id)
1282        if not repo:
1283            return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
1284
1285        username = request.user.username
1286        if not check_folder_permission(request, repo_id, '/'):
1287            return api_error(status.HTTP_403_FORBIDDEN,
1288                    'You do not have permission to access this library.')
1289
1290        # check whether user is repo owner
1291        if is_org_context(request):
1292            repo_owner = seafile_api.get_org_repo_owner(repo.id)
1293        else:
1294            repo_owner = seafile_api.get_repo_owner(repo.id)
1295        owner = "self" if username == repo_owner else "share"
1296
1297        last_commit = get_commits(repo.id, 0, 1)[0]
1298        repo.latest_modify = last_commit.ctime if last_commit else None
1299
1300        # query repo infomation
1301        repo.size = seafile_api.get_repo_size(repo_id)
1302        current_commit = get_commits(repo_id, 0, 1)[0]
1303        root_id = current_commit.root_id if current_commit else None
1304
1305        repo_json = {
1306            "type": "repo",
1307            "id": repo.id,
1308            "owner": owner,
1309            "name": repo.name,
1310            "mtime": repo.latest_modify,
1311            "size": repo.size,
1312            "encrypted": repo.encrypted,
1313            "root": root_id,
1314            "permission": check_permission(repo.id, username),
1315            "modifier_email": repo.last_modifier,
1316            "modifier_contact_email": email2contact_email(repo.last_modifier),
1317            "modifier_name": email2nickname(repo.last_modifier),
1318            "file_count": repo.file_count,
1319            }
1320        if repo.encrypted:
1321            repo_json["enc_version"] = repo.enc_version
1322            repo_json["salt"] = repo.salt if repo.enc_version >= 3 else ''
1323            repo_json["magic"] = repo.magic
1324            repo_json["random_key"] = repo.random_key
1325
1326        return Response(repo_json)
1327
1328    def post(self, request, repo_id, format=None):
1329        repo = get_repo(repo_id)
1330        if not repo:
1331            return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
1332
1333        op = request.GET.get('op', 'setpassword')
1334        if op == 'checkpassword':
1335            magic = request.GET.get('magic', default=None)
1336            if not magic:
1337                return api_error(HTTP_441_REPO_PASSWD_MAGIC_REQUIRED,
1338                                 'Library password magic is needed.')
1339
1340            if not check_folder_permission(request, repo_id, '/'):
1341                return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
1342
1343            try:
1344                seafile_api.check_passwd(repo.id, magic)
1345            except SearpcError as e:
1346                logger.error(e)
1347                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
1348                                 "SearpcError:" + e.msg)
1349            return Response("success")
1350        elif op == 'setpassword':
1351            resp = check_set_repo_password(request, repo)
1352            if resp:
1353                return resp
1354            return Response("success")
1355        elif op == 'rename':
1356            username = request.user.username
1357            repo_name = request.POST.get('repo_name')
1358            repo_desc = request.POST.get('repo_desc')
1359
1360            if not is_valid_dirent_name(repo_name):
1361                return api_error(status.HTTP_400_BAD_REQUEST,
1362                                 'name invalid.')
1363
1364            # check permission
1365            if is_org_context(request):
1366                repo_owner = seafile_api.get_org_repo_owner(repo.id)
1367            else:
1368                repo_owner = seafile_api.get_repo_owner(repo.id)
1369            is_owner = True if username == repo_owner else False
1370            if not is_owner:
1371                return api_error(status.HTTP_403_FORBIDDEN,
1372                        'You do not have permission to rename this library.')
1373
1374            # check repo status
1375            repo_status = repo.status
1376            if repo_status != 0:
1377                error_msg = 'Permission denied.'
1378                return api_error(status.HTTP_403_FORBIDDEN, error_msg)
1379
1380            if edit_repo(repo_id, repo_name, repo_desc, username):
1381                return Response("success")
1382            else:
1383                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
1384                                 "Unable to rename library")
1385
1386        return Response("unsupported operation")
1387
1388    def delete(self, request, repo_id, format=None):
1389        repo = seafile_api.get_repo(repo_id)
1390        if not repo:
1391            error_msg = 'Library %s not found.' % repo_id
1392            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1393
1394        # check permission
1395        org_id = None
1396        if is_org_context(request):
1397            org_id = request.user.org.org_id
1398            repo_owner = seafile_api.get_org_repo_owner(repo.id)
1399        else:
1400            repo_owner = seafile_api.get_repo_owner(repo.id)
1401
1402        username = request.user.username
1403        if username != repo_owner:
1404            error_msg = 'Permission denied.'
1405            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
1406
1407        try:
1408            usernames = get_related_users_by_repo(repo_id, org_id)
1409        except Exception as e:
1410            logger.error(e)
1411            usernames = []
1412
1413        # remove repo
1414        seafile_api.remove_repo(repo_id)
1415
1416        repo_deleted.send(sender=None,
1417                          org_id=org_id,
1418                          operator=username,
1419                          usernames=usernames,
1420                          repo_owner=repo_owner,
1421                          repo_id=repo_id,
1422                          repo_name=repo.name)
1423        return Response('success', status=status.HTTP_200_OK)
1424
1425class RepoHistory(APIView):
1426    authentication_classes = (TokenAuthentication, )
1427    permission_classes = (IsAuthenticated,)
1428    throttle_classes = (UserRateThrottle, )
1429
1430    def get(self, request, repo_id, format=None):
1431        try:
1432            current_page = int(request.GET.get('page', '1'))
1433            per_page = int(request.GET.get('per_page', '25'))
1434        except ValueError:
1435            current_page = 1
1436            per_page = 25
1437
1438        commits_all = get_commits(repo_id, per_page * (current_page - 1),
1439                                  per_page + 1)
1440        commits = commits_all[:per_page]
1441
1442        if len(commits_all) == per_page + 1:
1443            page_next = True
1444        else:
1445            page_next = False
1446
1447        return HttpResponse(json.dumps({"commits": commits,
1448                                        "page_next": page_next},
1449                                       cls=SearpcObjEncoder),
1450                            status=200, content_type=json_content_type)
1451
1452class RepoHistoryLimit(APIView):
1453    authentication_classes = (TokenAuthentication, SessionAuthentication)
1454    permission_classes = (IsAuthenticated,)
1455    throttle_classes = (UserRateThrottle, )
1456
1457    def get(self, request, repo_id, format=None):
1458
1459        repo = seafile_api.get_repo(repo_id)
1460        if not repo:
1461            error_msg = 'Library %s not found.' % repo_id
1462            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1463
1464        # check permission
1465        if is_org_context(request):
1466            repo_owner = seafile_api.get_org_repo_owner(repo_id)
1467        else:
1468            repo_owner = seafile_api.get_repo_owner(repo_id)
1469
1470        username = request.user.username
1471        # no settings for virtual repo
1472        if repo.is_virtual:
1473            error_msg = 'Permission denied.'
1474            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
1475
1476        if '@seafile_group' in repo_owner:
1477            group_id = get_group_id_by_repo_owner(repo_owner)
1478            if not is_group_admin(group_id, username):
1479                error_msg = 'Permission denied.'
1480                return api_error(status.HTTP_403_FORBIDDEN, error_msg)
1481        else:
1482            if username != repo_owner:
1483                error_msg = 'Permission denied.'
1484                return api_error(status.HTTP_403_FORBIDDEN, error_msg)
1485
1486        try:
1487            keep_days = seafile_api.get_repo_history_limit(repo_id)
1488            return Response({'keep_days': keep_days})
1489        except SearpcError as e:
1490            logger.error(e)
1491            error_msg = 'Internal Server Error'
1492            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
1493
1494    def put(self, request, repo_id, format=None):
1495
1496        repo = seafile_api.get_repo(repo_id)
1497        if not repo:
1498            error_msg = 'Library %s not found.' % repo_id
1499            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1500
1501        # check permission
1502        if is_org_context(request):
1503            repo_owner = seafile_api.get_org_repo_owner(repo_id)
1504        else:
1505            repo_owner = seafile_api.get_repo_owner(repo_id)
1506
1507        username = request.user.username
1508        # no settings for virtual repo
1509        if repo.is_virtual or not config.ENABLE_REPO_HISTORY_SETTING:
1510            error_msg = 'Permission denied.'
1511            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
1512
1513        if '@seafile_group' in repo_owner:
1514            group_id = get_group_id_by_repo_owner(repo_owner)
1515            if not is_group_admin(group_id, username):
1516                error_msg = 'Permission denied.'
1517                return api_error(status.HTTP_403_FORBIDDEN, error_msg)
1518        else:
1519            if username != repo_owner:
1520                error_msg = 'Permission denied.'
1521                return api_error(status.HTTP_403_FORBIDDEN, error_msg)
1522
1523        # check arg validation
1524        keep_days = request.data.get('keep_days', None)
1525        if not keep_days:
1526            error_msg = 'keep_days invalid.'
1527            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
1528
1529        try:
1530            keep_days = int(keep_days)
1531        except ValueError:
1532            error_msg = 'keep_days invalid.'
1533            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
1534
1535        try:
1536            # days <= -1, keep full history
1537            # days = 0, not keep history
1538            # days > 0, keep a period of days
1539            res = seafile_api.set_repo_history_limit(repo_id, keep_days)
1540        except SearpcError as e:
1541            logger.error(e)
1542            error_msg = 'Internal Server Error'
1543            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
1544
1545        if res == 0:
1546            new_limit = seafile_api.get_repo_history_limit(repo_id)
1547            return Response({'keep_days': new_limit})
1548        else:
1549            error_msg = 'Failed to set library history limit.'
1550            return api_error(status.HTTP_520_OPERATION_FAILED, error_msg)
1551
1552
1553class DownloadRepo(APIView):
1554    authentication_classes = (TokenAuthentication, SessionAuthentication)
1555    permission_classes = (IsAuthenticated, )
1556    throttle_classes = (UserRateThrottle, )
1557
1558    def get(self, request, repo_id, format=None):
1559
1560        repo = seafile_api.get_repo(repo_id)
1561        if not repo:
1562            error_msg = 'Library %s not found.' % repo_id
1563            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1564
1565        perm = check_folder_permission(request, repo_id, '/')
1566        if not perm:
1567            return api_error(status.HTTP_403_FORBIDDEN,
1568                    'You do not have permission to access this library.')
1569
1570        username = request.user.username
1571        if not seafile_api.is_repo_syncable(repo_id, username, perm):
1572            return api_error(status.HTTP_403_FORBIDDEN,
1573                             'unsyncable share permission')
1574
1575        return repo_download_info(request, repo_id)
1576
1577
1578class RepoOwner(APIView):
1579    authentication_classes = (TokenAuthentication, SessionAuthentication)
1580    permission_classes = (IsAuthenticated, )
1581    throttle_classes = (UserRateThrottle, )
1582
1583    def get(self, request, repo_id, format=None):
1584        repo = get_repo(repo_id)
1585        if not repo:
1586            error_msg = 'Library %s not found.' % repo_id
1587            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1588
1589        org_id = None
1590        if is_org_context(request):
1591            org_id = request.user.org.org_id
1592
1593        # check permission
1594        if org_id:
1595            repo_owner = seafile_api.get_org_repo_owner(repo_id)
1596        else:
1597            repo_owner = seafile_api.get_repo_owner(repo_id)
1598
1599        if request.user.username != repo_owner:
1600            error_msg = 'Permission denied.'
1601            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
1602
1603        return HttpResponse(json.dumps({"owner": repo_owner}), status=200,
1604                            content_type=json_content_type)
1605
1606    def put(self, request, repo_id, format=None):
1607        """ Currently only for transfer repo.
1608
1609        Permission checking:
1610        1. only repo owner can transfer repo.
1611        """
1612
1613        org_id = None
1614        if is_org_context(request):
1615            org_id = request.user.org.org_id
1616
1617        # argument check
1618        new_owner = request.data.get('owner', '').lower()
1619        if not new_owner:
1620            error_msg = 'owner invalid.'
1621            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
1622
1623        # resource check
1624        repo = seafile_api.get_repo(repo_id)
1625        if not repo:
1626            error_msg = 'Library %s not found.' % repo_id
1627            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1628
1629        try:
1630            new_owner_obj = User.objects.get(email=new_owner)
1631        except User.DoesNotExist:
1632            error_msg = 'User %s not found.' % new_owner
1633            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1634
1635        username = request.user.username
1636        if org_id:
1637            # transfer to department
1638            if '@seafile_group' in new_owner:
1639                group_id = get_group_id_by_repo_owner(new_owner)
1640                if not is_group_admin(group_id, username):
1641                    error_msg = 'Permission denied.'
1642                    return api_error(status.HTTP_403_FORBIDDEN, error_msg)
1643            # transfer to org user
1644            else:
1645                if not ccnet_api.org_user_exists(org_id, new_owner):
1646                    error_msg = _('User %s not found in organization.') % new_owner
1647                    return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1648
1649        # permission check
1650        if org_id:
1651            repo_owner = seafile_api.get_org_repo_owner(repo_id)
1652        else:
1653            repo_owner = seafile_api.get_repo_owner(repo_id)
1654
1655        if username != repo_owner:
1656            error_msg = 'Permission denied.'
1657            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
1658
1659        if not new_owner_obj.permissions.can_add_repo():
1660            error_msg = _('Transfer failed: role of %s is %s, can not add library.') % \
1661                    (new_owner, new_owner_obj.role)
1662            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
1663
1664        if new_owner == repo_owner:
1665            error_msg = _("Library can not be transferred to owner.")
1666            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
1667
1668        pub_repos = []
1669        if org_id:
1670            # get repo shared to user/group list
1671            shared_users = seafile_api.list_org_repo_shared_to(org_id,
1672                    repo_owner, repo_id)
1673            shared_groups = seafile_api.list_org_repo_shared_group(org_id,
1674                    repo_owner, repo_id)
1675
1676            # get all org pub repos
1677            pub_repos = seaserv.seafserv_threaded_rpc.list_org_inner_pub_repos_by_owner(
1678                    org_id, repo_owner)
1679        else:
1680            # get repo shared to user/group list
1681            shared_users = seafile_api.list_repo_shared_to(
1682                    repo_owner, repo_id)
1683            shared_groups = seafile_api.list_repo_shared_group_by_user(
1684                    repo_owner, repo_id)
1685
1686            # get all pub repos
1687            if not request.cloud_mode:
1688                pub_repos = seafile_api.list_inner_pub_repos_by_owner(repo_owner)
1689
1690        # transfer repo
1691        try:
1692            if org_id:
1693                if '@seafile_group' in new_owner:
1694                    group_id = int(new_owner.split('@')[0])
1695                    seafile_api.org_transfer_repo_to_group(repo_id, org_id, group_id, PERMISSION_READ_WRITE)
1696                else:
1697                    seafile_api.set_org_repo_owner(org_id, repo_id, new_owner)
1698            else:
1699                if ccnet_api.get_orgs_by_user(new_owner):
1700                    # can not transfer library to organization user %s.
1701                    error_msg = 'Email %s invalid.' % new_owner
1702                    return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
1703                else:
1704                    if '@seafile_group' in new_owner:
1705                        group_id = int(new_owner.split('@')[0])
1706                        seafile_api.transfer_repo_to_group(repo_id, group_id, PERMISSION_READ_WRITE)
1707                    else:
1708                        seafile_api.set_repo_owner(repo_id, new_owner)
1709        except SearpcError as e:
1710            logger.error(e)
1711            error_msg = 'Internal Server Error'
1712            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
1713
1714        # reshare repo to user
1715        for shared_user in shared_users:
1716            shared_username = shared_user.user
1717
1718            if new_owner == shared_username:
1719                continue
1720
1721            if org_id:
1722                seaserv.seafserv_threaded_rpc.org_add_share(org_id, repo_id,
1723                        new_owner, shared_username, shared_user.perm)
1724            else:
1725                seafile_api.share_repo(repo_id, new_owner,
1726                        shared_username, shared_user.perm)
1727
1728        # reshare repo to group
1729        for shared_group in shared_groups:
1730            shared_group_id = shared_group.group_id
1731
1732            if ('@seafile_group' not in new_owner) and\
1733                    (not is_group_member(shared_group_id, new_owner)):
1734                continue
1735
1736            if org_id:
1737                seafile_api.add_org_group_repo(repo_id, org_id,
1738                        shared_group_id, new_owner, shared_group.perm)
1739            else:
1740                seafile_api.set_group_repo(repo_id, shared_group_id,
1741                        new_owner, shared_group.perm)
1742
1743        # reshare repo to links
1744        try:
1745            UploadLinkShare.objects.filter(username=username, repo_id=repo_id).update(username=new_owner)
1746            FileShare.objects.filter(username=username, repo_id=repo_id).update(username=new_owner)
1747        except Exception as e:
1748            logger.error(e)
1749            error_msg = 'Internal Server Error'
1750            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
1751
1752        # check if current repo is pub-repo
1753        # if YES, reshare current repo to public
1754        for pub_repo in pub_repos:
1755            if repo_id != pub_repo.id:
1756                continue
1757
1758            if org_id:
1759                seafile_api.set_org_inner_pub_repo(org_id, repo_id,
1760                        pub_repo.permission)
1761            else:
1762                seaserv.seafserv_threaded_rpc.set_inner_pub_repo(
1763                        repo_id, pub_repo.permission)
1764
1765            break
1766
1767        # send a signal when successfully transfered repo
1768        try:
1769            repo_transfer.send(sender=None, org_id=org_id,
1770                    repo_owner=repo_owner, to_user=new_owner, repo_id=repo_id,
1771                    repo_name=repo.name)
1772        except Exception as e:
1773            logger.error(e)
1774
1775        return HttpResponse(json.dumps({'success': True}),
1776                content_type=json_content_type)
1777
1778########## File related
1779class FileBlockDownloadLinkView(APIView):
1780    authentication_classes = (TokenAuthentication, )
1781    permission_classes = (IsAuthenticated, )
1782    throttle_classes = (UserRateThrottle, )
1783
1784    def get(self, request, repo_id, file_id, block_id, format=None):
1785        # recourse check
1786        repo = seafile_api.get_repo(repo_id)
1787        if not repo:
1788            error_msg = 'Library %s not found.' % repo_id
1789            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1790
1791        parent_dir = request.GET.get('p', '/')
1792        dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir)
1793        if not dir_id:
1794            error_msg = 'Folder %s not found.' % parent_dir
1795            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1796
1797        # permission check
1798        if check_folder_permission(request, repo_id, parent_dir) is None:
1799            return api_error(status.HTTP_403_FORBIDDEN,
1800                    'You do not have permission to access this repo.')
1801
1802        if check_quota(repo_id) < 0:
1803            return api_error(HTTP_443_ABOVE_QUOTA, _("Out of quota."))
1804
1805        token = seafile_api.get_fileserver_access_token(
1806                repo_id, file_id, 'downloadblks', request.user.username)
1807
1808        if not token:
1809            error_msg = 'Internal Server Error'
1810            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
1811
1812        url = gen_block_get_url(token, block_id)
1813        return Response(url)
1814
1815class UploadLinkView(APIView):
1816    authentication_classes = (TokenAuthentication, SessionAuthentication)
1817    permission_classes = (IsAuthenticated,)
1818    throttle_classes = (UserRateThrottle,)
1819
1820    def get(self, request, repo_id, format=None):
1821        # recourse check
1822        repo = seafile_api.get_repo(repo_id)
1823        if not repo:
1824            error_msg = 'Library %s not found.' % repo_id
1825            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1826
1827        parent_dir = request.GET.get('p', '/')
1828        if not parent_dir:
1829            error_msg = 'p invalid.'
1830            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
1831
1832        dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir)
1833        if not dir_id:
1834            error_msg = 'Folder %s not found.' % parent_dir
1835            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1836
1837        # permission check
1838        if check_folder_permission(request, repo_id, parent_dir) != 'rw':
1839            return api_error(status.HTTP_403_FORBIDDEN,
1840                    'You do not have permission to access this folder.')
1841
1842        if check_quota(repo_id) < 0:
1843            return api_error(HTTP_443_ABOVE_QUOTA, _("Out of quota."))
1844
1845        obj_id = json.dumps({'parent_dir': parent_dir})
1846        token = seafile_api.get_fileserver_access_token(repo_id,
1847                obj_id, 'upload', request.user.username, use_onetime=False)
1848
1849        if not token:
1850            error_msg = 'Internal Server Error'
1851            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
1852        req_from = request.GET.get('from', 'api')
1853        if req_from == 'api':
1854            try:
1855                replace = to_python_boolean(request.GET.get('replace', '0'))
1856            except ValueError:
1857                replace = False
1858            url = gen_file_upload_url(token, 'upload-api', replace)
1859        elif req_from == 'web':
1860            url = gen_file_upload_url(token, 'upload-aj')
1861        else:
1862            error_msg = 'from invalid.'
1863            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
1864
1865        return Response(url)
1866
1867class UpdateLinkView(APIView):
1868    authentication_classes = (TokenAuthentication, SessionAuthentication)
1869    permission_classes = (IsAuthenticated,)
1870    throttle_classes = (UserRateThrottle,)
1871
1872    def get(self, request, repo_id, format=None):
1873        # recourse check
1874        repo = seafile_api.get_repo(repo_id)
1875        if not repo:
1876            error_msg = 'Library %s not found.' % repo_id
1877            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1878
1879        parent_dir = request.GET.get('p', '/')
1880        dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir)
1881        if not dir_id:
1882            error_msg = 'Folder %s not found.' % parent_dir
1883            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1884
1885        # permission check
1886        perm = check_folder_permission(request, repo_id, parent_dir)
1887        if perm not in (PERMISSION_PREVIEW_EDIT, PERMISSION_READ_WRITE):
1888            return api_error(status.HTTP_403_FORBIDDEN,
1889                    'You do not have permission to access this folder.')
1890
1891        if check_quota(repo_id) < 0:
1892            return api_error(HTTP_443_ABOVE_QUOTA, _("Out of quota."))
1893
1894        token = seafile_api.get_fileserver_access_token(repo_id,
1895                'dummy', 'update', request.user.username, use_onetime=False)
1896
1897        if not token:
1898            error_msg = 'Internal Server Error'
1899            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
1900
1901        req_from = request.GET.get('from', 'api')
1902        if req_from == 'api':
1903            url = gen_file_upload_url(token, 'update-api')
1904        elif req_from == 'web':
1905            url = gen_file_upload_url(token, 'update-aj')
1906        else:
1907            error_msg = 'from invalid.'
1908            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
1909
1910        return Response(url)
1911
1912class UploadBlksLinkView(APIView):
1913    authentication_classes = (TokenAuthentication, )
1914    permission_classes = (IsAuthenticated, )
1915    throttle_classes = (UserRateThrottle, )
1916
1917    def get(self, request, repo_id, format=None):
1918        # recourse check
1919        repo = seafile_api.get_repo(repo_id)
1920        if not repo:
1921            error_msg = 'Library %s not found.' % repo_id
1922            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1923
1924        parent_dir = request.GET.get('p', '/')
1925        dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir)
1926        if not dir_id:
1927            error_msg = 'Folder %s not found.' % parent_dir
1928            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1929
1930        # permission check
1931        if check_folder_permission(request, repo_id, parent_dir) != 'rw':
1932            return api_error(status.HTTP_403_FORBIDDEN,
1933                    'You do not have permission to access this folder.')
1934
1935        if check_quota(repo_id) < 0:
1936            return api_error(HTTP_443_ABOVE_QUOTA, _("Out of quota."))
1937
1938        obj_id = json.dumps({'parent_dir': parent_dir})
1939        token = seafile_api.get_fileserver_access_token(repo_id,
1940                obj_id, 'upload-blks-api', request.user.username, use_onetime=False)
1941
1942        if not token:
1943            error_msg = 'Internal Server Error'
1944            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
1945
1946        url = gen_file_upload_url(token, 'upload-blks-api')
1947        return Response(url)
1948
1949
1950    def get_blklist_missing(self, repo_id, blks):
1951        if not blks:
1952            return []
1953
1954        blklist = blks.split(',')
1955        try:
1956            return json.loads(seafile_api.check_repo_blocks_missing(
1957                repo_id, json.dumps(blklist)))
1958        except Exception as e:
1959            pass
1960
1961        return blklist
1962
1963    def post(self, request, repo_id, format=None):
1964        # recourse check
1965        repo = seafile_api.get_repo(repo_id)
1966        if not repo:
1967            error_msg = 'Library %s not found.' % repo_id
1968            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1969
1970        parent_dir = request.GET.get('p', '/')
1971        dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir)
1972        if not dir_id:
1973            error_msg = 'Folder %s not found.' % parent_dir
1974            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
1975
1976        # permission check
1977        if check_folder_permission(request, repo_id, parent_dir) != 'rw':
1978            return api_error(status.HTTP_403_FORBIDDEN,
1979                    'You do not have permission to access this folder.')
1980
1981        if check_quota(repo_id) < 0:
1982            return api_error(HTTP_443_ABOVE_QUOTA, _("Out of quota."))
1983
1984        obj_id = json.dumps({'parent_dir': parent_dir})
1985        token = seafile_api.get_fileserver_access_token(repo_id,
1986                obj_id, 'upload', request.user.username, use_onetime=False)
1987
1988        if not token:
1989            error_msg = 'Internal Server Error'
1990            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
1991
1992        blksurl = gen_file_upload_url(token, 'upload-raw-blks-api')
1993        commiturl = '%s?commitonly=true&ret-json=true' %  gen_file_upload_url(
1994            token, 'upload-blks-api')
1995        blks = request.POST.get('blklist', None)
1996        blklist = self.get_blklist_missing(repo_id, blks)
1997        res = {
1998            'rawblksurl': blksurl,
1999            'commiturl': commiturl,
2000            'blklist': blklist
2001        }
2002
2003        return HttpResponse(json.dumps(res), status=200,
2004                            content_type=json_content_type)
2005
2006
2007class UpdateBlksLinkView(APIView):
2008    authentication_classes = (TokenAuthentication, )
2009    permission_classes = (IsAuthenticated, )
2010    throttle_classes = (UserRateThrottle, )
2011
2012    def get(self, request, repo_id, format=None):
2013        # recourse check
2014        repo = seafile_api.get_repo(repo_id)
2015        if not repo:
2016            error_msg = 'Library %s not found.' % repo_id
2017            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
2018
2019        parent_dir = request.GET.get('p', '/')
2020        dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir)
2021        if not dir_id:
2022            error_msg = 'Folder %s not found.' % parent_dir
2023            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
2024
2025        # permission check
2026        if check_folder_permission(request, repo_id, parent_dir) != 'rw':
2027            return api_error(status.HTTP_403_FORBIDDEN,
2028                    'You do not have permission to access this folder.')
2029
2030        if check_quota(repo_id) < 0:
2031            return api_error(HTTP_443_ABOVE_QUOTA, _("Out of quota."))
2032
2033        token = seafile_api.get_fileserver_access_token(repo_id,
2034                'dummy', 'update-blks-api', request.user.username, use_onetime=False)
2035
2036        if not token:
2037            error_msg = 'Internal Server Error'
2038            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
2039
2040        url = gen_file_upload_url(token, 'update-blks-api')
2041        return Response(url)
2042
2043def get_dir_file_recursively(username, repo_id, path, all_dirs):
2044    is_pro = is_pro_version()
2045    path_id = seafile_api.get_dir_id_by_path(repo_id, path)
2046    dirs = seafile_api.list_dir_with_perm(repo_id, path,
2047            path_id, username, -1, -1)
2048
2049    for dirent in dirs:
2050        entry = {}
2051        if stat.S_ISDIR(dirent.mode):
2052            entry["type"] = 'dir'
2053        else:
2054            entry["type"] = 'file'
2055            entry['modifier_email'] = dirent.modifier
2056            entry["size"] = dirent.size
2057
2058            if is_pro:
2059                entry["is_locked"] = dirent.is_locked
2060                entry["lock_owner"] = dirent.lock_owner
2061                if dirent.lock_owner:
2062                    entry["lock_owner_name"] = email2nickname(dirent.lock_owner)
2063                entry["lock_time"] = dirent.lock_time
2064                if username == dirent.lock_owner:
2065                    entry["locked_by_me"] = True
2066                else:
2067                    entry["locked_by_me"] = False
2068
2069        entry["parent_dir"] = path
2070        entry["id"] = dirent.obj_id
2071        entry["name"] = dirent.obj_name
2072        entry["mtime"] = dirent.mtime
2073        entry["permission"] = dirent.permission
2074
2075        all_dirs.append(entry)
2076
2077        # Use dict to reduce memcache fetch cost in large for-loop.
2078        file_list =  [item for item in all_dirs if item['type'] == 'file']
2079        contact_email_dict = {}
2080        nickname_dict = {}
2081        modifiers_set = {x['modifier_email'] for x in file_list}
2082        for e in modifiers_set:
2083            if e not in contact_email_dict:
2084                contact_email_dict[e] = email2contact_email(e)
2085            if e not in nickname_dict:
2086                nickname_dict[e] = email2nickname(e)
2087
2088        for e in file_list:
2089            e['modifier_contact_email'] = contact_email_dict.get(e['modifier_email'], '')
2090            e['modifier_name'] = nickname_dict.get(e['modifier_email'], '')
2091
2092
2093        if stat.S_ISDIR(dirent.mode):
2094            sub_path = posixpath.join(path, dirent.obj_name)
2095            get_dir_file_recursively(username, repo_id, sub_path, all_dirs)
2096
2097    return all_dirs
2098
2099def get_dir_entrys_by_id(request, repo, path, dir_id, request_type=None):
2100    """ Get dirents in a dir
2101
2102    if request_type is 'f', only return file list,
2103    if request_type is 'd', only return dir list,
2104    else, return both.
2105    """
2106    username = request.user.username
2107    try:
2108        dirs = seafile_api.list_dir_with_perm(repo.id, path, dir_id,
2109                username, -1, -1)
2110        dirs = dirs if dirs else []
2111    except SearpcError as e:
2112        logger.error(e)
2113        return api_error(HTTP_520_OPERATION_FAILED,
2114                         "Failed to list dir.")
2115
2116    dir_list, file_list = [], []
2117    for dirent in dirs:
2118        entry = {}
2119        if stat.S_ISDIR(dirent.mode):
2120            dtype = "dir"
2121        else:
2122            dtype = "file"
2123            entry['modifier_email'] = dirent.modifier
2124            if repo.version == 0:
2125                entry["size"] = get_file_size(repo.store_id, repo.version,
2126                                              dirent.obj_id)
2127            else:
2128                entry["size"] = dirent.size
2129            if is_pro_version():
2130                entry["is_locked"] = dirent.is_locked
2131                entry["lock_owner"] = dirent.lock_owner
2132                if dirent.lock_owner:
2133                    entry["lock_owner_name"] = email2nickname(dirent.lock_owner)
2134                entry["lock_time"] = dirent.lock_time
2135                if username == dirent.lock_owner:
2136                    entry["locked_by_me"] = True
2137                else:
2138                    entry["locked_by_me"] = False
2139
2140        entry["type"] = dtype
2141        entry["name"] = dirent.obj_name
2142        entry["id"] = dirent.obj_id
2143        entry["mtime"] = dirent.mtime
2144        entry["permission"] = dirent.permission
2145        if dtype == 'dir':
2146            dir_list.append(entry)
2147        else:
2148            file_list.append(entry)
2149
2150    # Use dict to reduce memcache fetch cost in large for-loop.
2151    contact_email_dict = {}
2152    nickname_dict = {}
2153    modifiers_set = {x['modifier_email'] for x in file_list}
2154    for e in modifiers_set:
2155        if e not in contact_email_dict:
2156            contact_email_dict[e] = email2contact_email(e)
2157        if e not in nickname_dict:
2158            nickname_dict[e] = email2nickname(e)
2159
2160    starred_files = get_dir_starred_files(username, repo.id, path)
2161    files_tags_in_dir = get_files_tags_in_dir(repo.id, path)
2162
2163    for e in file_list:
2164        e['modifier_contact_email'] = contact_email_dict.get(e['modifier_email'], '')
2165        e['modifier_name'] = nickname_dict.get(e['modifier_email'], '')
2166
2167        file_tags = files_tags_in_dir.get(e['name'])
2168        if file_tags:
2169            e['file_tags'] = []
2170            for file_tag in file_tags:
2171                e['file_tags'].append(file_tag)
2172        file_path = posixpath.join(path, e['name'])
2173        e['starred'] = False
2174        if normalize_file_path(file_path) in starred_files:
2175            e['starred'] = True
2176
2177    dir_list.sort(key=lambda x: x['name'].lower())
2178    file_list.sort(key=lambda x: x['name'].lower())
2179
2180    if request_type == 'f':
2181        dentrys = file_list
2182    elif request_type == 'd':
2183        dentrys = dir_list
2184    else:
2185        dentrys = dir_list + file_list
2186
2187    response = HttpResponse(json.dumps(dentrys), status=200,
2188                            content_type=json_content_type)
2189    response["oid"] = dir_id
2190    response["dir_perm"] = seafile_api.check_permission_by_path(repo.id, path, username)
2191    return response
2192
2193def get_shared_link(request, repo_id, path):
2194    l = FileShare.objects.filter(repo_id=repo_id).filter(
2195        username=request.user.username).filter(path=path)
2196    token = None
2197    if len(l) > 0:
2198        fileshare = l[0]
2199        token = fileshare.token
2200    else:
2201        token = gen_token(max_length=10)
2202
2203        fs = FileShare()
2204        fs.username = request.user.username
2205        fs.repo_id = repo_id
2206        fs.path = path
2207        fs.token = token
2208
2209        try:
2210            fs.save()
2211        except IntegrityError as e:
2212            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, e.msg)
2213
2214    http_or_https = request.is_secure() and 'https' or 'http'
2215    domain = get_current_site(request).domain
2216    file_shared_link = '%s://%s%sf/%s/' % (http_or_https, domain,
2217                                           settings.SITE_ROOT, token)
2218    return file_shared_link
2219
2220def get_repo_file(request, repo_id, file_id, file_name, op,
2221                  use_onetime=dj_settings.FILESERVER_TOKEN_ONCE_ONLY):
2222    if op == 'download':
2223        token = seafile_api.get_fileserver_access_token(repo_id,
2224                file_id, op, request.user.username, use_onetime)
2225
2226        if not token:
2227            error_msg = 'Internal Server Error'
2228            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
2229
2230        redirect_url = gen_file_get_url(token, file_name)
2231        response = HttpResponse(json.dumps(redirect_url), status=200,
2232                                content_type=json_content_type)
2233        response["oid"] = file_id
2234        return response
2235
2236    if op == 'downloadblks':
2237        blklist = []
2238        encrypted = False
2239        enc_version = 0
2240        if file_id != EMPTY_SHA1:
2241            try:
2242                blks = seafile_api.list_blocks_by_file_id(repo_id, file_id)
2243                blklist = blks.split('\n')
2244            except SearpcError as e:
2245                logger.error(e)
2246                return api_error(HTTP_520_OPERATION_FAILED,
2247                                 'Failed to get file block list')
2248        blklist = [i for i in blklist if len(i) == 40]
2249        if len(blklist) > 0:
2250            repo = get_repo(repo_id)
2251            encrypted = repo.encrypted
2252            enc_version = repo.enc_version
2253
2254        res = {
2255            'file_id': file_id,
2256            'blklist': blklist,
2257            'encrypted': encrypted,
2258            'enc_version': enc_version,
2259            }
2260        response = HttpResponse(json.dumps(res), status=200,
2261                                content_type=json_content_type)
2262        response["oid"] = file_id
2263        return response
2264
2265    if op == 'sharelink':
2266        path = request.GET.get('p', None)
2267        if path is None:
2268            return api_error(status.HTTP_400_BAD_REQUEST, 'Path is missing.')
2269
2270        file_shared_link = get_shared_link(request, repo_id, path)
2271        return Response(file_shared_link)
2272
2273def reloaddir(request, repo, parent_dir):
2274    try:
2275        dir_id = seafile_api.get_dir_id_by_path(repo.id, parent_dir)
2276    except SearpcError as e:
2277        logger.error(e)
2278        return api_error(HTTP_520_OPERATION_FAILED,
2279                         "Failed to get dir id by path")
2280
2281    if not dir_id:
2282        return api_error(status.HTTP_404_NOT_FOUND, "Path does not exist")
2283
2284    return get_dir_entrys_by_id(request, repo, parent_dir, dir_id)
2285
2286def reloaddir_if_necessary(request, repo, parent_dir, obj_info=None):
2287
2288    reload_dir = False
2289    s = request.GET.get('reloaddir', None)
2290    if s and s.lower() == 'true':
2291        reload_dir = True
2292
2293    if not reload_dir:
2294        if obj_info:
2295            return Response(obj_info)
2296        else:
2297            return Response('success')
2298
2299    return reloaddir(request, repo, parent_dir)
2300
2301# deprecated
2302class OpDeleteView(APIView):
2303    """
2304    Delete files.
2305    """
2306    authentication_classes = (TokenAuthentication, SessionAuthentication)
2307    permission_classes = (IsAuthenticated, )
2308
2309    def post(self, request, repo_id, format=None):
2310
2311        parent_dir = request.GET.get('p')
2312        file_names = request.POST.get("file_names")
2313        if not parent_dir or not file_names:
2314            return api_error(status.HTTP_404_NOT_FOUND,
2315                             'File or directory not found.')
2316
2317        repo = get_repo(repo_id)
2318        if not repo:
2319            return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
2320
2321        username = request.user.username
2322        if check_folder_permission(request, repo_id, parent_dir) != 'rw':
2323            return api_error(status.HTTP_403_FORBIDDEN,
2324                             'You do not have permission to delete this file.')
2325
2326        allowed_file_names = []
2327        locked_files = get_locked_files_by_dir(request, repo_id, parent_dir)
2328        for file_name in file_names.split(':'):
2329            if file_name not in list(locked_files.keys()):
2330                # file is not locked
2331                allowed_file_names.append(file_name)
2332            elif locked_files[file_name] == username:
2333                # file is locked by current user
2334                allowed_file_names.append(file_name)
2335
2336        try:
2337            multi_files = "\t".join(allowed_file_names)
2338            seafile_api.del_file(repo_id, parent_dir,
2339                                 multi_files, username)
2340        except SearpcError as e:
2341            logger.error(e)
2342            return api_error(HTTP_520_OPERATION_FAILED,
2343                             "Failed to delete file.")
2344
2345        return reloaddir_if_necessary(request, repo, parent_dir)
2346
2347class OpMoveView(APIView):
2348    """
2349    Move files.
2350    """
2351    authentication_classes = (TokenAuthentication, SessionAuthentication)
2352    permission_classes = (IsAuthenticated, )
2353
2354    def post(self, request, repo_id, format=None):
2355
2356        username = request.user.username
2357        parent_dir = request.GET.get('p', '/')
2358        dst_repo = request.POST.get('dst_repo', None)
2359        dst_dir = request.POST.get('dst_dir', None)
2360        obj_names = request.POST.get("file_names", None)
2361
2362        # argument check
2363        if not parent_dir or not obj_names or not dst_repo or not dst_dir:
2364            return api_error(status.HTTP_400_BAD_REQUEST,
2365                             'Missing argument.')
2366
2367        if repo_id == dst_repo and parent_dir == dst_dir:
2368            return api_error(status.HTTP_400_BAD_REQUEST,
2369                             'The destination directory is the same as the source.')
2370
2371        # src resource check
2372        repo = get_repo(repo_id)
2373        if not repo:
2374            error_msg = 'Library %s not found.' % repo_id
2375            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
2376
2377        if not seafile_api.get_dir_id_by_path(repo_id, parent_dir):
2378            error_msg = 'Folder %s not found.' % parent_dir
2379            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
2380
2381        # dst resource check
2382        if not get_repo(dst_repo):
2383            error_msg = 'Library %s not found.' % dst_repo
2384            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
2385
2386        if not seafile_api.get_dir_id_by_path(dst_repo, dst_dir):
2387            error_msg = 'Folder %s not found.' % dst_dir
2388            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
2389
2390        # permission check
2391        if check_folder_permission(request, repo_id, parent_dir) != 'rw':
2392            return api_error(status.HTTP_403_FORBIDDEN,
2393                    'You do not have permission to move file in this folder.')
2394
2395        if check_folder_permission(request, dst_repo, dst_dir) != 'rw':
2396            return api_error(status.HTTP_403_FORBIDDEN,
2397                    'You do not have permission to move file to destination folder.')
2398
2399        allowed_obj_names = []
2400        locked_files = get_locked_files_by_dir(request, repo_id, parent_dir)
2401        for file_name in obj_names.split(':'):
2402            if file_name not in list(locked_files.keys()):
2403                # file is not locked
2404                allowed_obj_names.append(file_name)
2405            elif locked_files[file_name] == username:
2406                # file is locked by current user
2407                allowed_obj_names.append(file_name)
2408
2409        # check if all file/dir existes
2410        obj_names = allowed_obj_names
2411        dirents = seafile_api.list_dir_by_path(repo_id, parent_dir)
2412        exist_obj_names = [dirent.obj_name for dirent in dirents]
2413        if not set(obj_names).issubset(exist_obj_names):
2414            return api_error(status.HTTP_400_BAD_REQUEST,
2415                             'file_names invalid.')
2416
2417        # only check quota when move file/dir between different user's repo
2418        if get_repo_owner(request, repo_id) != get_repo_owner(request, dst_repo):
2419            # get total size of file/dir to be copied
2420            total_size = 0
2421            for obj_name in obj_names:
2422
2423                current_size = 0
2424                current_path = posixpath.join(parent_dir, obj_name)
2425
2426                current_file_id = seafile_api.get_file_id_by_path(repo_id,
2427                        current_path)
2428                if current_file_id:
2429                    current_size = seafile_api.get_file_size(repo.store_id,
2430                            repo.version, current_file_id)
2431
2432                total_size += current_size
2433
2434            # check if above quota for dst repo
2435            if seafile_api.check_quota(dst_repo, total_size) < 0:
2436                return api_error(HTTP_443_ABOVE_QUOTA, _("Out of quota."))
2437
2438        # make new name
2439        dst_dirents = seafile_api.list_dir_by_path(dst_repo, dst_dir)
2440        dst_obj_names = [dirent.obj_name for dirent in dst_dirents]
2441
2442        new_obj_names = []
2443        for obj_name in obj_names:
2444            new_obj_name = get_no_duplicate_obj_name(obj_name, dst_obj_names)
2445            new_obj_names.append(new_obj_name)
2446
2447        # move file
2448        try:
2449            src_multi_objs = "\t".join(obj_names)
2450            dst_multi_objs = "\t".join(new_obj_names)
2451
2452            seafile_api.move_file(repo_id, parent_dir, src_multi_objs,
2453                    dst_repo, dst_dir, dst_multi_objs, replace=False,
2454                    username=username, need_progress=0, synchronous=1)
2455        except SearpcError as e:
2456            logger.error(e)
2457            return api_error(HTTP_520_OPERATION_FAILED,
2458                             "Failed to move file.")
2459
2460        obj_info_list = []
2461        for new_obj_name in new_obj_names:
2462            obj_info = {}
2463            obj_info['repo_id'] = dst_repo
2464            obj_info['parent_dir'] = dst_dir
2465            obj_info['obj_name'] = new_obj_name
2466            obj_info_list.append(obj_info)
2467
2468        return reloaddir_if_necessary(request, repo, parent_dir, obj_info_list)
2469
2470
2471class OpCopyView(APIView):
2472    """
2473    Copy files.
2474    """
2475    authentication_classes = (TokenAuthentication, SessionAuthentication)
2476    permission_classes = (IsAuthenticated, )
2477
2478    def post(self, request, repo_id, format=None):
2479
2480        username = request.user.username
2481        parent_dir = request.GET.get('p', '/')
2482        dst_repo = request.POST.get('dst_repo', None)
2483        dst_dir = request.POST.get('dst_dir', None)
2484        obj_names = request.POST.get("file_names", None)
2485
2486        # argument check
2487        if not parent_dir or not obj_names or not dst_repo or not dst_dir:
2488            return api_error(status.HTTP_400_BAD_REQUEST,
2489                             'Missing argument.')
2490
2491        # src resource check
2492        repo = get_repo(repo_id)
2493        if not repo:
2494            error_msg = 'Library %s not found.' % repo_id
2495            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
2496
2497        if not seafile_api.get_dir_id_by_path(repo_id, parent_dir):
2498            error_msg = 'Folder %s not found.' % parent_dir
2499            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
2500
2501        # dst resource check
2502        if not get_repo(dst_repo):
2503            error_msg = 'Library %s not found.' % dst_repo
2504            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
2505
2506        if not seafile_api.get_dir_id_by_path(dst_repo, dst_dir):
2507            error_msg = 'Folder %s not found.' % dst_dir
2508            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
2509
2510        # permission check
2511        if parse_repo_perm(check_folder_permission(request, repo_id, parent_dir)).can_copy is False:
2512            return api_error(status.HTTP_403_FORBIDDEN,
2513                    'You do not have permission to copy file of this folder.')
2514
2515        if check_folder_permission(request, dst_repo, dst_dir) != 'rw':
2516            return api_error(status.HTTP_403_FORBIDDEN,
2517                    'You do not have permission to copy file to destination folder.')
2518
2519        # check if all file/dir existes
2520        obj_names = obj_names.strip(':').split(':')
2521        dirents = seafile_api.list_dir_by_path(repo_id, parent_dir)
2522        exist_obj_names = [dirent.obj_name for dirent in dirents]
2523        if not set(obj_names).issubset(exist_obj_names):
2524            return api_error(status.HTTP_400_BAD_REQUEST,
2525                             'file_names invalid.')
2526
2527        # get total size of file/dir to be copied
2528        total_size = 0
2529        for obj_name in obj_names:
2530
2531            current_size = 0
2532            current_path = posixpath.join(parent_dir, obj_name)
2533
2534            current_file_id = seafile_api.get_file_id_by_path(repo_id,
2535                    current_path)
2536            if current_file_id:
2537                current_size = seafile_api.get_file_size(repo.store_id,
2538                        repo.version, current_file_id)
2539
2540            total_size += current_size
2541
2542        # check if above quota for dst repo
2543        if seafile_api.check_quota(dst_repo, total_size) < 0:
2544            return api_error(HTTP_443_ABOVE_QUOTA, _("Out of quota."))
2545
2546        # make new name
2547        dst_dirents = seafile_api.list_dir_by_path(dst_repo, dst_dir)
2548        dst_obj_names = [dirent.obj_name for dirent in dst_dirents]
2549
2550        new_obj_names = []
2551        for obj_name in obj_names:
2552            new_obj_name = get_no_duplicate_obj_name(obj_name, dst_obj_names)
2553            new_obj_names.append(new_obj_name)
2554
2555        # copy file
2556        try:
2557            src_multi_objs = "\t".join(obj_names)
2558            dst_multi_objs = "\t".join(new_obj_names)
2559
2560            seafile_api.copy_file(repo_id, parent_dir, src_multi_objs,
2561                    dst_repo, dst_dir, dst_multi_objs, username, 0, synchronous=1)
2562        except SearpcError as e:
2563            logger.error(e)
2564            return api_error(HTTP_520_OPERATION_FAILED,
2565                             "Failed to copy file.")
2566
2567        obj_info_list = []
2568        for new_obj_name in new_obj_names:
2569            obj_info = {}
2570            obj_info['repo_id'] = dst_repo
2571            obj_info['parent_dir'] = dst_dir
2572            obj_info['obj_name'] = new_obj_name
2573            obj_info_list.append(obj_info)
2574
2575        return reloaddir_if_necessary(request, repo, parent_dir, obj_info_list)
2576
2577
2578class StarredFileView(APIView):
2579    """
2580    Support uniform interface for starred file operation,
2581    including add/delete/list starred files.
2582    """
2583
2584    authentication_classes = (TokenAuthentication, SessionAuthentication)
2585    permission_classes = (IsAuthenticated,)
2586    throttle_classes = (UserRateThrottle, )
2587
2588    def get(self, request, format=None):
2589        # list starred files
2590        personal_files = UserStarredFiles.objects.get_starred_files_by_username(
2591            request.user.username)
2592        starred_files = prepare_starred_files(personal_files)
2593        return Response(starred_files)
2594
2595    def post(self, request, format=None):
2596        # add starred file
2597        repo_id = request.POST.get('repo_id', '')
2598        path = request.POST.get('p', '')
2599
2600        if not (repo_id and path):
2601            return api_error(status.HTTP_400_BAD_REQUEST,
2602                             'Library ID or path is missing.')
2603
2604        if check_folder_permission(request, repo_id, path) is None:
2605            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied')
2606
2607        try:
2608            file_id = seafile_api.get_file_id_by_path(repo_id, path)
2609        except SearpcError as e:
2610            logger.error(e)
2611            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
2612
2613        if not file_id:
2614            return api_error(status.HTTP_404_NOT_FOUND, "File not found")
2615
2616        if path[-1] == '/':     # Should not contain '/' at the end of path.
2617            return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid file path.')
2618
2619        star_file(request.user.username, repo_id, path, is_dir=False,
2620                  org_id=-1)
2621
2622        resp = Response('success', status=status.HTTP_201_CREATED)
2623        resp['Location'] = reverse('starredfiles')
2624
2625        return resp
2626
2627    def delete(self, request, format=None):
2628        # remove starred file
2629        repo_id = request.GET.get('repo_id', '')
2630        path = request.GET.get('p', '')
2631
2632        if not (repo_id and path):
2633            return api_error(status.HTTP_400_BAD_REQUEST,
2634                             'Library ID or path is missing.')
2635
2636        if check_folder_permission(request, repo_id, path) is None:
2637            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied')
2638
2639        try:
2640            file_id = seafile_api.get_file_id_by_path(repo_id, path)
2641        except SearpcError as e:
2642            logger.error(e)
2643            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
2644
2645        if not file_id:
2646            return api_error(status.HTTP_404_NOT_FOUND, "File not found")
2647
2648        if path[-1] == '/':     # Should not contain '/' at the end of path.
2649            return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid file path.')
2650
2651        unstar_file(request.user.username, repo_id, path)
2652        return Response('success', status=status.HTTP_200_OK)
2653
2654class OwaFileView(APIView):
2655
2656    authentication_classes = (TokenAuthentication, SessionAuthentication)
2657    permission_classes = (IsAuthenticated, )
2658    throttle_classes = (UserRateThrottle, )
2659
2660    def get(self, request, repo_id, format=None):
2661        """ Get action url and access token when view file through Office Web App
2662        """
2663
2664        # check args
2665        repo = get_repo(repo_id)
2666        if not repo:
2667            error_msg = 'Library %s not found.' % repo_id
2668            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
2669
2670        action = request.GET.get('action', 'view')
2671        if action not in ('view', 'edit'):
2672            error_msg = 'action invalid.'
2673            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
2674
2675        path = request.GET.get('path', None)
2676        if not path:
2677            error_msg = 'path invalid.'
2678            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
2679
2680        try:
2681            file_id = seafile_api.get_file_id_by_path(repo_id, path)
2682        except SearpcError as e:
2683            logger.error(e)
2684            error_msg = 'Internal Server Error'
2685            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
2686
2687        if not file_id:
2688            error_msg = 'File %s not found.' % path
2689            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
2690
2691        # check permission
2692        if not is_pro_version():
2693            error_msg = 'Office Web App feature only supported in professional edition.'
2694            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
2695
2696        if check_folder_permission(request, repo_id, path) is None:
2697            error_msg = 'Permission denied.'
2698            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
2699
2700        if repo.encrypted:
2701            error_msg = 'Library encrypted.'
2702            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
2703
2704        if not ENABLE_OFFICE_WEB_APP:
2705            error_msg = 'Office Web App feature not enabled.'
2706            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
2707
2708        filename = os.path.basename(path)
2709        filetype, fileext = get_file_type_and_ext(filename)
2710        if fileext not in OFFICE_WEB_APP_FILE_EXTENSION:
2711            error_msg = 'path invalid.'
2712            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
2713
2714        # get wopi dict
2715        username = request.user.username
2716        wopi_dict = get_wopi_dict(username, repo_id, path,
2717                action_name=action, language_code=request.LANGUAGE_CODE)
2718
2719        # send stats message
2720        send_file_access_msg(request, repo, path, 'api')
2721        return Response(wopi_dict)
2722
2723
2724class DevicesView(APIView):
2725    authentication_classes = (TokenAuthentication, SessionAuthentication)
2726    permission_classes = (IsAuthenticated,)
2727    throttle_classes = (UserRateThrottle, )
2728
2729    def get(self, request, format=None):
2730        """ List user's devices.
2731
2732        Permission checking:
2733        1. All authenticated users.
2734        """
2735
2736        username = request.user.username
2737        devices = TokenV2.objects.get_user_devices(username)
2738
2739        for device in devices:
2740            device['last_accessed'] = datetime_to_isoformat_timestr(device['last_accessed'])
2741            device['is_desktop_client'] = False
2742            if device['platform'] in DESKTOP_PLATFORMS:
2743                device['is_desktop_client'] = True
2744
2745        return Response(devices)
2746
2747    def delete(self, request, format=None):
2748
2749        platform = request.data.get('platform', '')
2750        device_id = request.data.get('device_id', '')
2751        remote_wipe = request.data.get('wipe_device', '')
2752
2753        if not platform:
2754            error_msg = 'platform invalid.'
2755            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
2756
2757        if not device_id:
2758            error_msg = 'device_id invalid.'
2759            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
2760
2761        remote_wipe = True if remote_wipe == 'true' else False
2762
2763        try:
2764            do_unlink_device(request.user.username,
2765                             platform,
2766                             device_id,
2767                             remote_wipe=remote_wipe)
2768        except SearpcError as e:
2769            logger.error(e)
2770            error_msg = 'Internal Server Error'
2771            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
2772
2773        return Response({'success': True})
2774
2775
2776class FileMetaDataView(APIView):
2777    authentication_classes = (TokenAuthentication, SessionAuthentication)
2778    permission_classes = (IsAuthenticated, )
2779    throttle_classes = (UserRateThrottle, )
2780
2781    def get(self, request, repo_id, format=None):
2782        # file metadata
2783        repo = get_repo(repo_id)
2784        if not repo:
2785            return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
2786
2787        path = request.GET.get('p', None)
2788        if not path:
2789            return api_error(status.HTTP_400_BAD_REQUEST, 'Path is missing.')
2790
2791        parent_dir = os.path.dirname(path)
2792        if check_folder_permission(request, repo_id, parent_dir) is None:
2793            return api_error(status.HTTP_403_FORBIDDEN,
2794                    'You do not have permission to access this file.')
2795
2796        file_id = None
2797        try:
2798            file_id = seafile_api.get_file_id_by_path(repo_id, path)
2799        except SearpcError as e:
2800            logger.error(e)
2801            return api_error(HTTP_520_OPERATION_FAILED,
2802                             "Failed to get file id by path.")
2803
2804        if not file_id:
2805            return api_error(status.HTTP_404_NOT_FOUND, "File not found")
2806
2807        return Response({
2808            'id': file_id,
2809        })
2810
2811
2812class FileView(APIView):
2813    """
2814    Support uniform interface for file related operations,
2815    including create/delete/rename/view, etc.
2816    """
2817
2818    authentication_classes = (TokenAuthentication, SessionAuthentication)
2819    permission_classes = (IsAuthenticated, )
2820    throttle_classes = (UserRateThrottle, )
2821
2822    def get(self, request, repo_id, format=None):
2823        # view file
2824        repo = get_repo(repo_id)
2825        if not repo:
2826            return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
2827
2828        path = request.GET.get('p', None)
2829        if not path:
2830            return api_error(status.HTTP_400_BAD_REQUEST, 'Path is missing.')
2831
2832        parent_dir = os.path.dirname(path)
2833        if check_folder_permission(request, repo_id, parent_dir) is None:
2834            return api_error(status.HTTP_403_FORBIDDEN,
2835                    'You do not have permission to access this file.')
2836
2837        file_id = None
2838        try:
2839            file_id = seafile_api.get_file_id_by_path(repo_id, path)
2840        except SearpcError as e:
2841            logger.error(e)
2842            return api_error(HTTP_520_OPERATION_FAILED,
2843                             "Failed to get file id by path.")
2844
2845        if not file_id:
2846            return api_error(status.HTTP_404_NOT_FOUND, "File not found")
2847
2848        # send stats message
2849        send_file_access_msg(request, repo, path, 'api')
2850
2851        file_name = os.path.basename(path)
2852        op = request.GET.get('op', 'download')
2853
2854        reuse = request.GET.get('reuse', '0')
2855        if reuse not in ('1', '0'):
2856            return api_error(status.HTTP_400_BAD_REQUEST,
2857                    "If you want to reuse file server access token for download file, you should set 'reuse' argument as '1'.")
2858
2859        use_onetime = False if reuse == '1' else True
2860        return get_repo_file(request, repo_id, file_id,
2861                file_name, op, use_onetime)
2862
2863    def post(self, request, repo_id, format=None):
2864        # rename, move, copy or create file
2865        repo = get_repo(repo_id)
2866        if not repo:
2867            return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
2868
2869        path = request.GET.get('p', '')
2870        if not path or path[0] != '/':
2871            return api_error(status.HTTP_400_BAD_REQUEST,
2872                             'Path is missing or invalid.')
2873
2874        username = request.user.username
2875        parent_dir = os.path.dirname(path)
2876        operation = request.POST.get('operation', '')
2877        is_draft = request.POST.get('is_draft', '')
2878
2879        file_info = {}
2880        if operation.lower() == 'rename':
2881            if check_folder_permission(request, repo_id, parent_dir) != 'rw':
2882                return api_error(status.HTTP_403_FORBIDDEN,
2883                                 'You do not have permission to rename file.')
2884
2885            newname = request.POST.get('newname', '')
2886            if not newname:
2887                return api_error(status.HTTP_400_BAD_REQUEST,
2888                                 'New name is missing')
2889
2890            if len(newname) > settings.MAX_UPLOAD_FILE_NAME_LEN:
2891                return api_error(status.HTTP_400_BAD_REQUEST, 'New name is too long')
2892
2893            if not seafile_api.is_valid_filename('fake_repo_id', newname):
2894                error_msg = 'File name invalid.'
2895                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
2896
2897            oldname = os.path.basename(path)
2898            if oldname == newname:
2899                return api_error(status.HTTP_409_CONFLICT,
2900                                 'The new name is the same to the old')
2901
2902            newname = check_filename_with_rename(repo_id, parent_dir, newname)
2903            try:
2904                seafile_api.rename_file(repo_id, parent_dir, oldname, newname,
2905                                        username)
2906            except SearpcError as e:
2907                return api_error(HTTP_520_OPERATION_FAILED,
2908                                 "Failed to rename file: %s" % e)
2909
2910            if request.GET.get('reloaddir', '').lower() == 'true':
2911                return reloaddir(request, repo, parent_dir)
2912            else:
2913                resp = Response('success', status=status.HTTP_301_MOVED_PERMANENTLY)
2914                uri = reverse('FileView', args=[repo_id], request=request)
2915                resp['Location'] = uri + '?p=' + quote(parent_dir.encode('utf-8')) + quote(newname.encode('utf-8'))
2916                return resp
2917
2918        elif operation.lower() == 'move':
2919            if check_folder_permission(request, repo_id, parent_dir) != 'rw':
2920                return api_error(status.HTTP_403_FORBIDDEN,
2921                                 'You do not have permission to move file.')
2922
2923            src_dir = os.path.dirname(path)
2924            src_repo_id = repo_id
2925            dst_repo_id = request.POST.get('dst_repo', '')
2926            dst_dir = request.POST.get('dst_dir', '')
2927            if dst_dir[-1] != '/': # Append '/' to the end of directory if necessary
2928                dst_dir += '/'
2929            # obj_names   = request.POST.get('obj_names', '')
2930
2931            if not (dst_repo_id and dst_dir):
2932                return api_error(status.HTTP_400_BAD_REQUEST, 'Missing arguments.')
2933
2934            if src_repo_id == dst_repo_id and src_dir == dst_dir:
2935                return Response('success', status=status.HTTP_200_OK)
2936
2937            if check_folder_permission(request, dst_repo_id, dst_dir) != 'rw':
2938                return api_error(status.HTTP_403_FORBIDDEN,
2939                                 'You do not have permission to move file.')
2940
2941            filename = os.path.basename(path)
2942            new_filename = check_filename_with_rename(dst_repo_id, dst_dir, filename)
2943            try:
2944                seafile_api.move_file(src_repo_id, src_dir,
2945                                      filename, dst_repo_id,
2946                                      dst_dir, new_filename,
2947                                      replace=False, username=username,
2948                                      need_progress=0, synchronous=1)
2949            except SearpcError as e:
2950                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
2951                                 "SearpcError:" + e.msg)
2952
2953            dst_repo = get_repo(dst_repo_id)
2954            if not dst_repo:
2955                return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
2956
2957            if request.GET.get('reloaddir', '').lower() == 'true':
2958                return reloaddir(request, dst_repo, dst_dir)
2959            else:
2960                file_info['repo_id'] = dst_repo_id
2961                file_info['parent_dir'] = dst_dir
2962                file_info['obj_name'] = new_filename
2963                resp = Response(file_info, status=status.HTTP_301_MOVED_PERMANENTLY)
2964                uri = reverse('FileView', args=[dst_repo_id], request=request)
2965                resp['Location'] = uri + '?p=' + quote(dst_dir.encode('utf-8')) + quote(new_filename.encode('utf-8'))
2966                return resp
2967
2968        elif operation.lower() == 'copy':
2969            src_repo_id = repo_id
2970            src_dir = os.path.dirname(path)
2971            dst_repo_id = request.POST.get('dst_repo', '')
2972            dst_dir = request.POST.get('dst_dir', '')
2973
2974            if dst_dir[-1] != '/': # Append '/' to the end of directory if necessary
2975                dst_dir += '/'
2976
2977            if not (dst_repo_id and dst_dir):
2978                return api_error(status.HTTP_400_BAD_REQUEST, 'Missing arguments.')
2979
2980            # check src folder permission
2981
2982            if parse_repo_perm(check_folder_permission(request, repo_id, src_dir)).can_copy is False:
2983                return api_error(status.HTTP_403_FORBIDDEN,
2984                                 'You do not have permission to copy file.')
2985
2986            # check dst folder permission
2987            if check_folder_permission(request, dst_repo_id, dst_dir) != 'rw':
2988                return api_error(status.HTTP_403_FORBIDDEN,
2989                                 'You do not have permission to copy file.')
2990
2991            filename = os.path.basename(path)
2992            new_filename = check_filename_with_rename(dst_repo_id, dst_dir, filename)
2993            try:
2994                seafile_api.copy_file(src_repo_id, src_dir,
2995                                      filename, dst_repo_id,
2996                                      dst_dir, new_filename,
2997                                      username, 0, synchronous=1)
2998            except SearpcError as e:
2999                logger.error(e)
3000                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
3001                                 "SearpcError:" + e.msg)
3002
3003            if request.GET.get('reloaddir', '').lower() == 'true':
3004                return reloaddir(request, dst_repo, dst_dir)
3005            else:
3006                file_info['repo_id'] = dst_repo_id
3007                file_info['parent_dir'] = dst_dir
3008                file_info['obj_name'] = new_filename
3009                resp = Response(file_info, status=status.HTTP_200_OK)
3010                uri = reverse('FileView', args=[dst_repo_id], request=request)
3011                resp['Location'] = uri + '?p=' + quote(dst_dir.encode('utf-8')) + quote(new_filename.encode('utf-8'))
3012                return resp
3013
3014        elif operation.lower() == 'create':
3015            if check_folder_permission(request, repo_id, parent_dir) != 'rw':
3016                return api_error(status.HTTP_403_FORBIDDEN,
3017                                 'You do not have permission to create file.')
3018
3019            if is_draft.lower() == 'true':
3020                file_name = os.path.basename(path)
3021                file_dir = os.path.dirname(path)
3022
3023                draft_type = os.path.splitext(file_name)[0][-7:]
3024                file_type = os.path.splitext(file_name)[-1]
3025
3026                if draft_type != '(draft)':
3027                    f = os.path.splitext(file_name)[0]
3028                    path = file_dir + '/' + f + '(draft)' + file_type
3029
3030            new_file_name = os.path.basename(path)
3031
3032            if not seafile_api.is_valid_filename('fake_repo_id', new_file_name):
3033                error_msg = 'File name invalid.'
3034                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3035
3036            new_file_name = check_filename_with_rename(repo_id, parent_dir, new_file_name)
3037
3038            try:
3039                seafile_api.post_empty_file(repo_id, parent_dir,
3040                                            new_file_name, username)
3041            except SearpcError as e:
3042                return api_error(HTTP_520_OPERATION_FAILED,
3043                                 'Failed to create file.')
3044
3045            if is_draft.lower() == 'true':
3046                Draft.objects.add(username, repo, path, file_exist=False)
3047
3048            if request.GET.get('reloaddir', '').lower() == 'true':
3049                return reloaddir(request, repo, parent_dir)
3050            else:
3051                resp = Response('success', status=status.HTTP_201_CREATED)
3052                uri = reverse('FileView', args=[repo_id], request=request)
3053                resp['Location'] = uri + '?p=' + quote(parent_dir.encode('utf-8')) + \
3054                    quote(new_file_name.encode('utf-8'))
3055                return resp
3056        else:
3057            return api_error(status.HTTP_400_BAD_REQUEST,
3058                             "Operation can only be rename, create or move.")
3059
3060    def put(self, request, repo_id, format=None):
3061        repo = get_repo(repo_id)
3062        if not repo:
3063            return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
3064
3065        path = request.data.get('p', '')
3066        file_id = seafile_api.get_file_id_by_path(repo_id, path)
3067        if not path or not file_id:
3068            return api_error(status.HTTP_400_BAD_REQUEST,
3069                             'Path is missing or invalid.')
3070
3071        username = request.user.username
3072        # check file access permission
3073        parent_dir = os.path.dirname(path)
3074        if check_folder_permission(request, repo_id, parent_dir) != 'rw':
3075            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
3076
3077        # check file lock
3078        try:
3079            is_locked, locked_by_me = check_file_lock(repo_id, path, username)
3080        except Exception as e:
3081            logger.error(e)
3082            error_msg = 'Internal Server Error'
3083            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
3084
3085        operation = request.data.get('operation', '')
3086        if operation.lower() == 'lock':
3087            if is_locked:
3088                return api_error(status.HTTP_403_FORBIDDEN, 'File is already locked')
3089
3090            # lock file
3091            expire = request.data.get('expire', FILE_LOCK_EXPIRATION_DAYS)
3092            try:
3093                seafile_api.lock_file(repo_id, path.lstrip('/'), username, expire)
3094                return Response('success', status=status.HTTP_200_OK)
3095            except SearpcError as e:
3096                logger.error(e)
3097                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
3098
3099        if operation.lower() == 'unlock':
3100            if not is_locked:
3101                return api_error(status.HTTP_403_FORBIDDEN, 'File is not locked')
3102            if not locked_by_me:
3103                return api_error(status.HTTP_403_FORBIDDEN, 'You can not unlock this file')
3104
3105            # unlock file
3106            try:
3107                seafile_api.unlock_file(repo_id, path.lstrip('/'))
3108                return Response('success', status=status.HTTP_200_OK)
3109            except SearpcError as e:
3110                logger.error(e)
3111                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
3112        else:
3113            return api_error(status.HTTP_400_BAD_REQUEST,
3114                             "Operation can only be lock or unlock")
3115
3116    def delete(self, request, repo_id, format=None):
3117        # delete file
3118        repo = get_repo(repo_id)
3119        if not repo:
3120            return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
3121
3122        path = request.GET.get('p', None)
3123        if not path:
3124            return api_error(status.HTTP_400_BAD_REQUEST, 'Path is missing.')
3125
3126        parent_dir = os.path.dirname(path)
3127        if check_folder_permission(request, repo_id, parent_dir) != 'rw':
3128            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
3129
3130        parent_dir = os.path.dirname(path)
3131        file_name = os.path.basename(path)
3132
3133        try:
3134            seafile_api.del_file(repo_id, parent_dir,
3135                                 file_name, request.user.username)
3136        except SearpcError as e:
3137            logger.error(e)
3138            return api_error(HTTP_520_OPERATION_FAILED,
3139                             "Failed to delete file.")
3140
3141        return reloaddir_if_necessary(request, repo, parent_dir)
3142
3143class FileDetailView(APIView):
3144    authentication_classes = (TokenAuthentication, SessionAuthentication)
3145    permission_classes = (IsAuthenticated,)
3146    throttle_classes = (UserRateThrottle,)
3147
3148    def get(self, request, repo_id, format=None):
3149
3150        # argument check
3151        path = request.GET.get('p', None)
3152        if not path:
3153            error_msg = 'p invalid.'
3154            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3155
3156        path = normalize_file_path(path)
3157
3158        # resource check
3159        repo = seafile_api.get_repo(repo_id)
3160        if not repo:
3161            error_msg = 'Library %s not found.' % repo_id
3162            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3163
3164        commit_id = request.GET.get('commit_id', None)
3165        try:
3166            if commit_id:
3167                obj_id = seafile_api.get_file_id_by_commit_and_path(repo_id,
3168                        commit_id, path)
3169            else:
3170                obj_id = seafile_api.get_file_id_by_path(repo_id, path)
3171        except Exception as e:
3172            logger.error(e)
3173            error_msg = 'Internal Server Error'
3174            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
3175
3176        if not obj_id:
3177            error_msg = 'File %s not found.' % path
3178            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3179
3180        # permission check
3181        parent_dir = os.path.dirname(path)
3182        permission = check_folder_permission(request, repo_id, parent_dir)
3183        if not permission:
3184            error_msg = 'Permission denied.'
3185            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3186
3187        # get real path for sub repo
3188        if repo.is_virtual:
3189            real_path = posixpath.join(repo.origin_path, path.lstrip('/'))
3190            real_repo_id = repo.origin_repo_id
3191        else:
3192            real_path = path
3193            real_repo_id = repo_id
3194
3195        file_name = os.path.basename(path)
3196        entry = {}
3197        entry["type"] = "file"
3198        entry["id"] = obj_id
3199        entry["name"] = file_name
3200        entry["permission"] = permission
3201
3202        file_type, file_ext = get_file_type_and_ext(file_name)
3203        if file_type == MARKDOWN:
3204            is_draft = is_draft_file(repo_id, path)
3205
3206            has_draft = False
3207            if not is_draft:
3208                has_draft = has_draft_file(repo_id, path)
3209
3210            draft = get_file_draft(repo_id, path, is_draft, has_draft)
3211
3212            entry['is_draft'] = is_draft
3213            entry['has_draft'] = has_draft
3214            entry['draft_file_path'] = draft['draft_file_path']
3215            entry['draft_id'] = draft['draft_id']
3216
3217        # fetch file contributors and latest contributor
3218        try:
3219            # get real path for sub repo
3220            dirent = seafile_api.get_dirent_by_path(real_repo_id, real_path)
3221        except Exception as e:
3222            logger.error(e)
3223            dirent = None
3224
3225        last_modified = dirent.mtime if dirent else ''
3226        latest_contributor = dirent.modifier if dirent else ''
3227
3228        entry["mtime"] = last_modified
3229        entry["last_modified"] = timestamp_to_isoformat_timestr(last_modified)
3230        entry["last_modifier_email"] = latest_contributor
3231        entry["last_modifier_name"] = email2nickname(latest_contributor)
3232        entry["last_modifier_contact_email"] = email2contact_email(latest_contributor)
3233
3234        try:
3235            file_size = get_file_size(real_repo_id, repo.version, obj_id)
3236        except Exception as e:
3237            logger.error(e)
3238            file_size = 0
3239        entry["size"] = file_size
3240
3241        starred_files = UserStarredFiles.objects.filter(repo_id=repo_id,
3242                path=path)
3243        entry["starred"] = True if len(starred_files) > 0 else False
3244        file_comments = FileComment.objects.get_by_file_path(repo_id, path)
3245        comment_total = file_comments.count()
3246        entry["comment_total"] = comment_total
3247
3248        entry["can_edit"], _ = can_edit_file(file_name, file_size, repo)
3249
3250        return Response(entry)
3251
3252
3253class FileRevert(APIView):
3254    authentication_classes = (TokenAuthentication, SessionAuthentication)
3255    permission_classes = (IsAuthenticated,)
3256    throttle_classes = (UserRateThrottle, )
3257
3258    def put(self, request, repo_id, format=None):
3259        path = request.data.get('p', None)
3260        commit_id = request.data.get('commit_id', None)
3261
3262        if not path:
3263            error_msg = 'path invalid.'
3264            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3265
3266        if not commit_id:
3267            error_msg = 'commit_id invalid.'
3268            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3269
3270        if not seafile_api.get_repo(repo_id):
3271            error_msg = 'library %s not found.' % repo_id
3272            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3273
3274        if not seafile_api.get_file_id_by_commit_and_path(repo_id, commit_id, path):
3275            error_msg = 'file %s not found.' % path
3276            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3277
3278        if check_folder_permission(request, repo_id, '/') != 'rw':
3279            error_msg = 'Permission denied.'
3280            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3281
3282        username = request.user.username
3283        try:
3284            seafile_api.revert_file(repo_id, commit_id, path, username)
3285        except SearpcError as e:
3286            logger.error(e)
3287            error_msg = 'Internal Server Error'
3288            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
3289
3290        return Response({'success': True})
3291
3292
3293class FileRevision(APIView):
3294    authentication_classes = (TokenAuthentication, SessionAuthentication)
3295    permission_classes = (IsAuthenticated,)
3296    throttle_classes = (UserRateThrottle, )
3297
3298    def get(self, request, repo_id, format=None):
3299        path = request.GET.get('p', None)
3300        if path is None:
3301            return api_error(status.HTTP_400_BAD_REQUEST, 'Path is missing.')
3302
3303        file_name = os.path.basename(path)
3304        commit_id = request.GET.get('commit_id', None)
3305
3306        try:
3307            obj_id = seafserv_threaded_rpc.get_file_id_by_commit_and_path(
3308                repo_id, commit_id, path)
3309        except:
3310            return api_error(status.HTTP_404_NOT_FOUND, 'Revision not found.')
3311
3312        return get_repo_file(request, repo_id, obj_id, file_name, 'download')
3313
3314class FileHistory(APIView):
3315    authentication_classes = (TokenAuthentication,)
3316    permission_classes = (IsAuthenticated,)
3317    throttle_classes = (UserRateThrottle,)
3318
3319    def get(self, request, repo_id, format=None):
3320        """ Get file history.
3321        """
3322
3323        path = request.GET.get('p', None)
3324        if path is None:
3325            error_msg = 'p invalid.'
3326            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3327
3328        repo = seafile_api.get_repo(repo_id)
3329        if not repo:
3330            error_msg = 'Library %s not found.' % repo_id
3331            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3332
3333        file_id = seafile_api.get_file_id_by_path(repo_id, path)
3334        if not file_id:
3335            error_msg = 'File %s not found.' % path
3336            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3337
3338
3339        permission = check_folder_permission(request, repo_id, path)
3340        if permission not in get_available_repo_perms():
3341
3342            error_msg = 'Permission denied.'
3343            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3344
3345        try:
3346            commits = get_file_revisions_after_renamed(repo_id, path)
3347        except Exception as e:
3348            logger.error(e)
3349            error_msg = 'Internal Server Error'
3350            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
3351
3352        for commit in commits:
3353            creator_name = commit.creator_name
3354
3355            user_info = {}
3356            user_info['email'] = creator_name
3357            user_info['name'] = email2nickname(creator_name)
3358            user_info['contact_email'] = Profile.objects.get_contact_email_by_user(creator_name)
3359
3360            commit._dict['user_info'] = user_info
3361
3362        return HttpResponse(json.dumps({"commits": commits},
3363            cls=SearpcObjEncoder), status=200, content_type=json_content_type)
3364
3365class FileSharedLinkView(APIView):
3366    """
3367    Support uniform interface for file shared link.
3368    """
3369    authentication_classes = (TokenAuthentication, SessionAuthentication)
3370    permission_classes = (IsAuthenticated, )
3371    throttle_classes = (UserRateThrottle, )
3372
3373    def put(self, request, repo_id, format=None):
3374
3375        repo = seaserv.get_repo(repo_id)
3376        if not repo:
3377            return api_error(status.HTTP_404_NOT_FOUND, "Library does not exist")
3378
3379        if repo.encrypted:
3380            error_msg = 'Permission denied.'
3381            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3382
3383        path = request.data.get('p', None)
3384        if not path:
3385            return api_error(status.HTTP_400_BAD_REQUEST, 'Path is missing')
3386
3387        username = request.user.username
3388        password = request.data.get('password', None)
3389        share_type = request.data.get('share_type', 'download')
3390
3391        if password and len(password) < config.SHARE_LINK_PASSWORD_MIN_LENGTH:
3392            return api_error(status.HTTP_400_BAD_REQUEST, 'Password is too short')
3393
3394        if share_type.lower() == 'download':
3395            if parse_repo_perm(check_folder_permission(request, repo_id, path)).can_download is False:
3396                return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied')
3397
3398            if not request.user.permissions.can_generate_share_link():
3399                error_msg = 'Can not generate share link.'
3400                return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3401
3402            try:
3403                expire_days = int(request.data.get('expire', 0))
3404            except ValueError:
3405                error_msg = 'expire invalid.'
3406                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3407
3408            if expire_days <= 0:
3409                if SHARE_LINK_EXPIRE_DAYS_DEFAULT > 0:
3410                    expire_days = SHARE_LINK_EXPIRE_DAYS_DEFAULT
3411
3412            if SHARE_LINK_EXPIRE_DAYS_MIN > 0:
3413                if expire_days < SHARE_LINK_EXPIRE_DAYS_MIN:
3414                    error_msg = _('Expire days should be greater or equal to %s') % \
3415                            SHARE_LINK_EXPIRE_DAYS_MIN
3416                    return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3417
3418            if SHARE_LINK_EXPIRE_DAYS_MAX > 0:
3419                if expire_days > SHARE_LINK_EXPIRE_DAYS_MAX:
3420                    error_msg = _('Expire days should be less than or equal to %s') % \
3421                            SHARE_LINK_EXPIRE_DAYS_MAX
3422                    return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3423
3424            if expire_days <= 0:
3425                expire_date = None
3426            else:
3427                expire_date = timezone.now() + relativedelta(days=expire_days)
3428
3429            is_dir = False
3430            if path == '/':
3431                is_dir = True
3432            else:
3433                try:
3434                    real_path = repo.origin_path + path if repo.origin_path else path
3435                    dirent = seafile_api.get_dirent_by_path(repo.store_id, real_path)
3436                except SearpcError as e:
3437                    logger.error(e)
3438                    return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal Server Error")
3439
3440                if not dirent:
3441                    return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid path')
3442
3443                if stat.S_ISDIR(dirent.mode):
3444                    is_dir = True
3445
3446            if is_dir:
3447                # generate dir download link
3448                fs = FileShare.objects.get_dir_link_by_path(username, repo_id, path)
3449                if fs is None:
3450                    fs = FileShare.objects.create_dir_link(username, repo_id, path,
3451                                                           password, expire_date)
3452                    if is_org_context(request):
3453                        org_id = request.user.org.org_id
3454                        OrgFileShare.objects.set_org_file_share(org_id, fs)
3455
3456            else:
3457                # generate file download link
3458                fs = FileShare.objects.get_file_link_by_path(username, repo_id, path)
3459                if fs is None:
3460                    fs = FileShare.objects.create_file_link(username, repo_id, path,
3461                                                            password, expire_date)
3462                    if is_org_context(request):
3463                        org_id = request.user.org.org_id
3464                        OrgFileShare.objects.set_org_file_share(org_id, fs)
3465
3466            token = fs.token
3467            shared_link = gen_shared_link(token, fs.s_type)
3468
3469        elif share_type.lower() == 'upload':
3470            if not seafile_api.get_dir_id_by_path(repo_id, path):
3471                return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid path')
3472
3473            if check_folder_permission(request, repo_id, path) != 'rw':
3474                return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied')
3475
3476            if not request.user.permissions.can_generate_upload_link():
3477                error_msg = 'Can not generate upload link.'
3478                return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3479
3480            # generate upload link
3481            uls = UploadLinkShare.objects.get_upload_link_by_path(username, repo_id, path)
3482            if uls is None:
3483                uls = UploadLinkShare.objects.create_upload_link_share(
3484                    username, repo_id, path, password)
3485
3486            token = uls.token
3487            shared_link = gen_shared_upload_link(token)
3488
3489        else:
3490            return api_error(status.HTTP_400_BAD_REQUEST,
3491                             "Operation can only be download or upload.")
3492
3493        resp = Response(status=status.HTTP_201_CREATED)
3494        resp['Location'] = shared_link
3495        return resp
3496
3497########## Directory related
3498class DirMetaDataView(APIView):
3499    authentication_classes = (TokenAuthentication, SessionAuthentication)
3500    permission_classes = (IsAuthenticated, )
3501    throttle_classes = (UserRateThrottle, )
3502
3503    def get(self, request, repo_id, format=None):
3504        # recource check
3505        repo = seafile_api.get_repo(repo_id)
3506        if not repo:
3507            error_msg = 'Library %s not found.' % repo_id
3508            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3509
3510        path = request.GET.get('p', '/')
3511        path = normalize_dir_path(path)
3512
3513        dir_id = seafile_api.get_dir_id_by_path(repo_id, path)
3514        if not dir_id:
3515            error_msg = 'Folder %s not found.' % path
3516            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3517
3518        # permission check
3519        permission = check_folder_permission(request, repo_id, path)
3520        if not permission:
3521            error_msg = 'Permission denied.'
3522            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3523
3524        return Response({
3525            'id': dir_id,
3526        })
3527
3528
3529class DirView(APIView):
3530    """
3531    Support uniform interface for directory operations, including
3532    create/delete/rename/list, etc.
3533    """
3534    authentication_classes = (TokenAuthentication, SessionAuthentication)
3535    permission_classes = (IsAuthenticated, )
3536    throttle_classes = (UserRateThrottle, )
3537
3538    def get(self, request, repo_id, format=None):
3539
3540        # argument check
3541        recursive = request.GET.get('recursive', '0')
3542        if recursive not in ('1', '0'):
3543            error_msg = "If you want to get recursive dir entries, you should set 'recursive' argument as '1'."
3544            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3545
3546        request_type = request.GET.get('t', '')
3547        if request_type and request_type not in ('f', 'd'):
3548            error_msg = "'t'(type) should be 'f' or 'd'."
3549            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3550
3551        # recource check
3552        repo = seafile_api.get_repo(repo_id)
3553        if not repo:
3554            error_msg = 'Library %s not found.' % repo_id
3555            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3556
3557        path = request.GET.get('p', '/')
3558        path = normalize_dir_path(path)
3559
3560        dir_id = seafile_api.get_dir_id_by_path(repo_id, path)
3561        if not dir_id:
3562            error_msg = 'Folder %s not found.' % path
3563            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3564
3565        # permission check
3566        permission = check_folder_permission(request, repo_id, path)
3567        if parse_repo_perm(permission).can_download is False and \
3568           not is_web_request(request):
3569            # preview only repo and this request does not came from web brower
3570            error_msg = 'Permission denied.'
3571            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3572
3573        old_oid = request.GET.get('oid', None)
3574        if old_oid and old_oid == dir_id:
3575            response = HttpResponse(json.dumps("uptodate"), status=200,
3576                                    content_type=json_content_type)
3577            response["oid"] = dir_id
3578            return response
3579
3580        if recursive == '1':
3581            result = []
3582            username = request.user.username
3583            dir_file_list = get_dir_file_recursively(username, repo_id, path, [])
3584            if request_type == 'f':
3585                for item in dir_file_list:
3586                    if item['type'] == 'file':
3587                        result.append(item)
3588            elif request_type == 'd':
3589                for item in dir_file_list:
3590                    if item['type'] == 'dir':
3591                        result.append(item)
3592            else:
3593                result = dir_file_list
3594
3595            response = HttpResponse(json.dumps(result), status=200,
3596                                    content_type=json_content_type)
3597            response["oid"] = dir_id
3598            response["dir_perm"] = permission
3599            return response
3600
3601        return get_dir_entrys_by_id(request, repo, path, dir_id, request_type)
3602
3603    def post(self, request, repo_id, format=None):
3604        # new dir
3605        repo = get_repo(repo_id)
3606        if not repo:
3607            return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
3608
3609        path = request.GET.get('p', '')
3610
3611        if not path or path[0] != '/':
3612            return api_error(status.HTTP_400_BAD_REQUEST, "Path is missing.")
3613        if path == '/':         # Can not make or rename root dir.
3614            return api_error(status.HTTP_400_BAD_REQUEST, "Path is invalid.")
3615        if path[-1] == '/':     # Cut out last '/' if possible.
3616            path = path[:-1]
3617
3618        username = request.user.username
3619        operation = request.POST.get('operation', '')
3620        parent_dir = os.path.dirname(path)
3621
3622        if operation.lower() == 'mkdir':
3623            new_dir_name = os.path.basename(path)
3624
3625            if not seafile_api.is_valid_filename('fake_repo_id', new_dir_name):
3626                error_msg = 'Folder name invalid.'
3627                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3628
3629            create_parents = request.POST.get('create_parents', '').lower() in ('true', '1')
3630            if not create_parents:
3631                # check whether parent dir exists
3632                if not seafile_api.get_dir_id_by_path(repo_id, parent_dir):
3633                    error_msg = 'Folder %s not found.' % parent_dir
3634                    return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3635
3636                if check_folder_permission(request, repo_id, parent_dir) != 'rw':
3637                    error_msg = 'Permission denied.'
3638                    return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3639
3640                retry_count = 0
3641                while retry_count < 10:
3642                    new_dir_name = check_filename_with_rename(repo_id,
3643                            parent_dir, new_dir_name)
3644                    try:
3645                        seafile_api.post_dir(repo_id,
3646                                parent_dir, new_dir_name, username)
3647                        break
3648                    except SearpcError as e:
3649                        if str(e) == 'file already exists':
3650                            retry_count += 1
3651                        else:
3652                            logger.error(e)
3653                            return api_error(HTTP_520_OPERATION_FAILED,
3654                                         'Failed to make directory.')
3655            else:
3656                if check_folder_permission(request, repo_id, '/') != 'rw':
3657                    error_msg = 'Permission denied.'
3658                    return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3659
3660                try:
3661                    seafile_api.mkdir_with_parents(repo_id, '/',
3662                                                   path[1:], username)
3663                except SearpcError as e:
3664                    logger.error(e)
3665                    return api_error(HTTP_520_OPERATION_FAILED,
3666                                     'Failed to make directory.')
3667
3668            if request.GET.get('reloaddir', '').lower() == 'true':
3669                resp = reloaddir(request, repo, parent_dir)
3670            else:
3671                resp = Response('success', status=status.HTTP_201_CREATED)
3672                uri = reverse('DirView', args=[repo_id], request=request)
3673                resp['Location'] = uri + '?p=' + quote(
3674                    parent_dir.encode('utf-8') + '/'.encode('utf-8') + new_dir_name.encode('utf-8'))
3675            return resp
3676
3677        elif operation.lower() == 'rename':
3678            if not seafile_api.get_dir_id_by_path(repo_id, path):
3679                error_msg = 'Folder %s not found.' % path
3680                return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3681
3682            if check_folder_permission(request, repo.id, path) != 'rw':
3683                error_msg = 'Permission denied.'
3684                return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3685
3686            old_dir_name = os.path.basename(path)
3687
3688            newname = request.POST.get('newname', '')
3689            if not newname:
3690                return api_error(status.HTTP_400_BAD_REQUEST, "New name is mandatory.")
3691
3692            if newname == old_dir_name:
3693                return Response('success', status=status.HTTP_200_OK)
3694
3695            try:
3696                # rename duplicate name
3697                checked_newname = check_filename_with_rename(
3698                    repo_id, parent_dir, newname)
3699                # rename dir
3700                seafile_api.rename_file(repo_id, parent_dir, old_dir_name,
3701                                        checked_newname, username)
3702                return Response('success', status=status.HTTP_200_OK)
3703            except SearpcError as e:
3704                logger.error(e)
3705                return api_error(HTTP_520_OPERATION_FAILED,
3706                                 'Failed to rename folder.')
3707        # elif operation.lower() == 'move':
3708        #     pass
3709        else:
3710            return api_error(status.HTTP_400_BAD_REQUEST,
3711                             "Operation not supported.")
3712
3713    def delete(self, request, repo_id, format=None):
3714        # delete dir or file
3715        path = request.GET.get('p', None)
3716        if not path:
3717            error_msg = 'p invalid.'
3718            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3719
3720        repo = get_repo(repo_id)
3721        if not repo:
3722            error_msg = 'Library %s not found.' % repo_id
3723            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3724
3725        if not seafile_api.get_dir_id_by_path(repo_id, path):
3726            error_msg = 'Folder %s not found.' % path
3727            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3728
3729        if check_folder_permission(request, repo_id, path) != 'rw':
3730            error_msg = 'Permission denied.'
3731            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3732
3733        if path == '/':         # Can not delete root path.
3734            return api_error(status.HTTP_400_BAD_REQUEST, 'Path is invalid.')
3735
3736        if path[-1] == '/':     # Cut out last '/' if possible.
3737            path = path[:-1]
3738
3739        parent_dir = os.path.dirname(path)
3740        file_name = os.path.basename(path)
3741
3742        username = request.user.username
3743        try:
3744            seafile_api.del_file(repo_id, parent_dir,
3745                                 file_name, username)
3746        except SearpcError as e:
3747            logger.error(e)
3748            return api_error(HTTP_520_OPERATION_FAILED,
3749                             "Failed to delete file.")
3750
3751        return reloaddir_if_necessary(request, repo, parent_dir)
3752
3753class DirRevert(APIView):
3754    authentication_classes = (TokenAuthentication, SessionAuthentication)
3755    permission_classes = (IsAuthenticated,)
3756    throttle_classes = (UserRateThrottle, )
3757
3758    def put(self, request, repo_id):
3759        path = request.data.get('p', None)
3760        commit_id = request.data.get('commit_id', None)
3761
3762        if not path:
3763            error_msg = 'path invalid.'
3764            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3765
3766        if not commit_id:
3767            error_msg = 'commit_id invalid.'
3768            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3769
3770        if not seafile_api.get_repo(repo_id):
3771            error_msg = 'library %s not found.' % repo_id
3772            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3773
3774        if not seafile_api.get_dir_id_by_commit_and_path(repo_id, commit_id, path):
3775            error_msg = 'folder %s not found.' % path
3776            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3777
3778        if check_folder_permission(request, repo_id, '/') != 'rw':
3779            error_msg = 'Permission denied.'
3780            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3781
3782        username = request.user.username
3783        try:
3784            seafile_api.revert_dir(repo_id, commit_id, path, username)
3785        except SearpcError as e:
3786            logger.error(e)
3787            error_msg = 'Internal Server Error'
3788            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
3789
3790        return Response({'success': True})
3791
3792
3793class DirSubRepoView(APIView):
3794    authentication_classes = (TokenAuthentication, SessionAuthentication)
3795    permission_classes = (IsAuthenticated,)
3796    throttle_classes = (UserRateThrottle,)
3797
3798    def get(self, request, repo_id, format=None):
3799        """ Create sub-repo for folder
3800
3801        Permission checking:
3802        1. user with `r` or `rw` permission.
3803        2. password correct for encrypted repo.
3804        """
3805
3806        # argument check
3807        path = request.GET.get('p', None)
3808        if not path:
3809            error_msg = 'p invalid.'
3810            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3811
3812        name = request.GET.get('name', None)
3813        if not name:
3814            error_msg = 'name invalid.'
3815            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3816
3817        # recourse check
3818        repo = get_repo(repo_id)
3819        if not repo:
3820            error_msg = 'Library %s not found.' % repo_id
3821            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
3822
3823        # permission check
3824        if not check_folder_permission(request, repo_id, path):
3825            error_msg = 'Permission denied.'
3826            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
3827
3828        username = request.user.username
3829        password = request.GET.get('password', '')
3830        if repo.encrypted:
3831            # check password for encrypted repo
3832            if not password:
3833                error_msg = 'password invalid.'
3834                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3835            else:
3836                try:
3837                    seafile_api.set_passwd(repo_id, username, password)
3838                except SearpcError as e:
3839                    if e.msg == 'Bad arguments':
3840                        error_msg = 'Bad arguments'
3841                        return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3842                    elif e.msg == 'Incorrect password':
3843                        error_msg = _('Wrong password')
3844                        return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
3845                    elif e.msg == 'Internal server error':
3846                        error_msg = _('Internal Server Error')
3847                        return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
3848                    else:
3849                        error_msg = _('Decrypt library error')
3850                        return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
3851
3852            # create sub-lib for encrypted repo
3853            try:
3854                if is_org_context(request):
3855                    org_id = request.user.org.org_id
3856                    sub_repo_id = seafile_api.create_org_virtual_repo(
3857                            org_id, repo_id, path, name, name, username, password)
3858                else:
3859                    sub_repo_id = seafile_api.create_virtual_repo(
3860                            repo_id, path, name, name, username, password)
3861            except SearpcError as e:
3862                logger.error(e)
3863                error_msg = 'Internal Server Error'
3864                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
3865        else:
3866            # create sub-lib for common repo
3867            try:
3868                if is_org_context(request):
3869                    org_id = request.user.org.org_id
3870                    sub_repo_id = seafile_api.create_org_virtual_repo(
3871                            org_id, repo_id, path, name, name, username)
3872                else:
3873                    sub_repo_id = seafile_api.create_virtual_repo(
3874                            repo_id, path, name, name, username)
3875            except SearpcError as e:
3876                logger.error(e)
3877                error_msg = 'Internal Server Error'
3878                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
3879
3880        return Response({'sub_repo_id': sub_repo_id})
3881
3882########## Sharing
3883class SharedRepos(APIView):
3884    """
3885    List repos that a user share to others/groups/public.
3886    """
3887    authentication_classes = (TokenAuthentication, )
3888    permission_classes = (IsAuthenticated, )
3889    throttle_classes = (UserRateThrottle, )
3890
3891    def get(self, request, format=None):
3892        username = request.user.username
3893        shared_repos = []
3894
3895        shared_repos += list_share_repos(username, 'from_email', -1, -1)
3896        shared_repos += get_group_repos_by_owner(username)
3897        if not CLOUD_MODE:
3898            shared_repos += seafile_api.list_inner_pub_repos_by_owner(username)
3899
3900        return HttpResponse(json.dumps(shared_repos, cls=SearpcObjEncoder),
3901                            status=200, content_type=json_content_type)
3902
3903class BeSharedRepos(APIView):
3904    """
3905    List repos that others/groups share to user.
3906    """
3907    authentication_classes = (TokenAuthentication, SessionAuthentication )
3908    permission_classes = (IsAuthenticated, )
3909    throttle_classes = (UserRateThrottle, )
3910
3911    def get(self, request, format=None):
3912        username = request.user.username
3913        shared_repos = []
3914        shared_repos += seafile_api.get_share_in_repo_list(username, -1, -1)
3915
3916        joined_groups = ccnet_api.get_groups(username)
3917        for grp in joined_groups:
3918            # Get group repos, and for each group repos...
3919            for r_id in get_group_repoids(grp.id):
3920                # No need to list my own repo
3921                if seafile_api.is_repo_owner(username, r_id):
3922                    continue
3923                # Convert repo properties due to the different collumns in Repo
3924                # and SharedRepo
3925                r = get_repo(r_id)
3926                if not r:
3927                    continue
3928                r.repo_id = r.id
3929                r.repo_name = r.name
3930                r.repo_desc = r.desc
3931                cmmts = get_commits(r_id, 0, 1)
3932                last_commit = cmmts[0] if cmmts else None
3933                r.last_modified = last_commit.ctime if last_commit else 0
3934                r._dict['share_type'] = 'group'
3935                r.user = seafile_api.get_repo_owner(r_id)
3936                r.user_perm = check_permission(r_id, username)
3937                shared_repos.append(r)
3938
3939        if not CLOUD_MODE:
3940            shared_repos += seafile_api.get_inner_pub_repo_list()
3941
3942        return HttpResponse(json.dumps(shared_repos, cls=SearpcObjEncoder),
3943                            status=200, content_type=json_content_type)
3944
3945
3946class SharedFileView(APIView):
3947    # Anyone should be able to access a Shared File assuming they have the token
3948    throttle_classes = (UserRateThrottle, )
3949
3950    def get(self, request, token, format=None):
3951        assert token is not None    # Checked by URLconf
3952
3953        try:
3954            fileshare = FileShare.objects.get(token=token)
3955        except FileShare.DoesNotExist:
3956            return api_error(status.HTTP_404_NOT_FOUND, "Token not found")
3957
3958        repo_id = fileshare.repo_id
3959        repo = get_repo(repo_id)
3960        if not repo:
3961            return api_error(status.HTTP_404_NOT_FOUND, "Library not found")
3962
3963        path = fileshare.path.rstrip('/') # Normalize file path
3964        file_name = os.path.basename(path)
3965
3966        file_id = None
3967        try:
3968            file_id = seafile_api.get_file_id_by_path(repo_id, path)
3969        except SearpcError as e:
3970            logger.error(e)
3971            return api_error(HTTP_520_OPERATION_FAILED,
3972                             "Failed to get file id by path.")
3973
3974        if not file_id:
3975            return api_error(status.HTTP_404_NOT_FOUND, "File not found")
3976
3977        # Increase file shared link view_cnt, this operation should be atomic
3978        fileshare.view_cnt = F('view_cnt') + 1
3979        fileshare.save()
3980
3981        op = request.GET.get('op', 'download')
3982        return get_repo_file(request, repo_id, file_id, file_name, op)
3983
3984class SharedFileDetailView(APIView):
3985    throttle_classes = (UserRateThrottle, )
3986
3987    def get(self, request, token, format=None):
3988        assert token is not None    # Checked by URLconf
3989
3990        try:
3991            fileshare = FileShare.objects.get(token=token)
3992        except FileShare.DoesNotExist:
3993            return api_error(status.HTTP_404_NOT_FOUND, "Token not found")
3994
3995        if fileshare.is_encrypted():
3996            password = request.GET.get('password', '')
3997
3998            if not password:
3999                return api_error(status.HTTP_403_FORBIDDEN, "Password is required")
4000
4001            if not check_password(password, fileshare.password):
4002                return api_error(status.HTTP_403_FORBIDDEN, "Invalid Password")
4003
4004        repo_id = fileshare.repo_id
4005        repo = get_repo(repo_id)
4006        if not repo:
4007            return api_error(status.HTTP_404_NOT_FOUND, "Library not found")
4008
4009        path = fileshare.path.rstrip('/') # Normalize file path
4010        file_name = os.path.basename(path)
4011
4012        file_id = None
4013        try:
4014            file_id = seafile_api.get_file_id_by_path(repo_id, path)
4015            commits = get_file_revisions_after_renamed(repo_id, path)
4016            c = commits[0]
4017        except SearpcError as e:
4018            return api_error(HTTP_520_OPERATION_FAILED,
4019                             "Failed to get file id by path.")
4020
4021        if not file_id:
4022            return api_error(status.HTTP_404_NOT_FOUND, "File not found")
4023
4024        entry = {}
4025        try:
4026            entry["size"] = get_file_size(repo.store_id, repo.version, file_id)
4027        except Exception as e:
4028            logger.error(e)
4029            entry["size"] = 0
4030
4031        entry["type"] = "file"
4032        entry["name"] = file_name
4033        entry["id"] = file_id
4034        entry["mtime"] = c.ctime
4035        entry["repo_id"] = repo_id
4036        entry["path"] = path
4037
4038        return HttpResponse(json.dumps(entry), status=200,
4039                            content_type=json_content_type)
4040
4041
4042class FileShareEncoder(json.JSONEncoder):
4043    def default(self, obj):
4044        if not isinstance(obj, FileShare):
4045            return None
4046        return {'username':obj.username, 'repo_id':obj.repo_id,
4047                'path':obj.path, 'token':obj.token,
4048                'ctime':obj.ctime, 'view_cnt':obj.view_cnt,
4049                's_type':obj.s_type}
4050
4051class SharedLinksView(APIView):
4052    authentication_classes = (TokenAuthentication, SessionAuthentication )
4053    permission_classes = (IsAuthenticated,)
4054    throttle_classes = (UserRateThrottle, )
4055
4056    def get(self, request, format=None):
4057        username = request.user.username
4058
4059        fileshares = FileShare.objects.filter(username=username)
4060        p_fileshares = []           # personal file share
4061        for fs in fileshares:
4062            if is_personal_repo(fs.repo_id):  # only list files in personal repos
4063                r = seafile_api.get_repo(fs.repo_id)
4064                if not r:
4065                    fs.delete()
4066                    continue
4067
4068                if fs.s_type == 'f':
4069                    if seafile_api.get_file_id_by_path(r.id, fs.path) is None:
4070                        fs.delete()
4071                        continue
4072                    fs.filename = os.path.basename(fs.path)
4073                    fs.shared_link = gen_file_share_link(fs.token)
4074                else:
4075                    if seafile_api.get_dir_id_by_path(r.id, fs.path) is None:
4076                        fs.delete()
4077                        continue
4078                    fs.filename = os.path.basename(fs.path.rstrip('/'))
4079                    fs.shared_link = gen_dir_share_link(fs.token)
4080                fs.repo = r
4081                p_fileshares.append(fs)
4082        return HttpResponse(json.dumps({"fileshares": p_fileshares}, cls=FileShareEncoder), status=200, content_type=json_content_type)
4083
4084    def delete(self, request, format=None):
4085        token = request.GET.get('t', None)
4086        if not token:
4087            return api_error(status.HTTP_400_BAD_REQUEST, 'Token is missing')
4088
4089        username = request.user.username
4090        share = FileShare.objects.filter(token=token).filter(username=username) or \
4091                UploadLinkShare.objects.filter(token=token).filter(username=username)
4092
4093        if not share:
4094            return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid token')
4095
4096        share.delete()
4097
4098        return HttpResponse(json.dumps({}), status=200, content_type=json_content_type)
4099
4100class SharedDirView(APIView):
4101    throttle_classes = (UserRateThrottle, )
4102
4103    def get(self, request, token, format=None):
4104        """List dirents in dir download shared link
4105        """
4106        fileshare = FileShare.objects.get_valid_dir_link_by_token(token)
4107        if not fileshare:
4108            return api_error(status.HTTP_400_BAD_REQUEST, "Invalid token")
4109
4110        repo_id = fileshare.repo_id
4111        repo = get_repo(repo_id)
4112        if not repo:
4113            return api_error(status.HTTP_400_BAD_REQUEST, "Invalid token")
4114
4115        if fileshare.is_encrypted():
4116            password = request.GET.get('password', '')
4117
4118            if not password:
4119                return api_error(status.HTTP_403_FORBIDDEN, "Password is required")
4120
4121            if not check_password(password, fileshare.password):
4122                return api_error(status.HTTP_403_FORBIDDEN, "Invalid Password")
4123
4124        req_path = request.GET.get('p', '/')
4125        req_path = normalize_dir_path(req_path)
4126
4127        if req_path == '/':
4128            real_path = fileshare.path
4129        else:
4130            real_path = posixpath.join(fileshare.path, req_path.lstrip('/'))
4131
4132        real_path = normalize_dir_path(real_path)
4133
4134        dir_id = seafile_api.get_dir_id_by_path(repo_id, real_path)
4135        if not dir_id:
4136            return api_error(status.HTTP_400_BAD_REQUEST, "Invalid path")
4137
4138        username = fileshare.username
4139        try:
4140            dirs = seafserv_threaded_rpc.list_dir_with_perm(repo_id, real_path, dir_id,
4141                    username, -1, -1)
4142            dirs = dirs if dirs else []
4143        except SearpcError as e:
4144            logger.error(e)
4145            return api_error(HTTP_520_OPERATION_FAILED, "Failed to list dir.")
4146
4147        dir_list, file_list = [], []
4148        for dirent in dirs:
4149            dtype = "file"
4150            entry = {}
4151            if stat.S_ISDIR(dirent.mode):
4152                dtype = "dir"
4153            else:
4154                if repo.version == 0:
4155                    entry["size"] = get_file_size(repo.store_id, repo.version,
4156                                                  dirent.obj_id)
4157                else:
4158                    entry["size"] = dirent.size
4159
4160            entry["type"] = dtype
4161            entry["name"] = dirent.obj_name
4162            entry["id"] = dirent.obj_id
4163            entry["mtime"] = dirent.mtime
4164            if dtype == 'dir':
4165                dir_list.append(entry)
4166            else:
4167                file_list.append(entry)
4168
4169        dir_list.sort(key=lambda x: x['name'].lower())
4170        file_list.sort(key=lambda x: x['name'].lower())
4171        dentrys = dir_list + file_list
4172
4173        content_type = 'application/json; charset=utf-8'
4174        return HttpResponse(json.dumps(dentrys), status=200, content_type=content_type)
4175
4176class DefaultRepoView(APIView):
4177    """
4178    Get user's default library.
4179    """
4180    authentication_classes = (TokenAuthentication, SessionAuthentication)
4181    permission_classes = (IsAuthenticated, )
4182    throttle_classes = (UserRateThrottle, )
4183
4184    def get(self, request, format=None):
4185        username = request.user.username
4186        repo_id = UserOptions.objects.get_default_repo(username)
4187        if repo_id is None or (get_repo(repo_id) is None):
4188            json = {
4189                'exists': False,
4190            }
4191            return Response(json)
4192        else:
4193            return self.default_repo_info(repo_id)
4194
4195    def default_repo_info(self, repo_id):
4196        repo_json = {
4197            'exists': False,
4198        }
4199
4200        if repo_id is not None:
4201            repo_json['exists'] = True
4202            repo_json['repo_id'] = repo_id
4203
4204        return Response(repo_json)
4205
4206    def post(self, request):
4207        if not request.user.permissions.can_add_repo():
4208            return api_error(status.HTTP_403_FORBIDDEN,
4209                             'You do not have permission to create library.')
4210
4211        username = request.user.username
4212
4213        repo_id = UserOptions.objects.get_default_repo(username)
4214        if repo_id and (get_repo(repo_id) is not None):
4215            return self.default_repo_info(repo_id)
4216
4217        repo_id = create_default_library(request)
4218
4219        return self.default_repo_info(repo_id)
4220
4221class SharedRepo(APIView):
4222    """
4223    Support uniform interface for shared libraries.
4224    """
4225    authentication_classes = (TokenAuthentication, SessionAuthentication )
4226    permission_classes = (IsAuthenticated, )
4227    throttle_classes = (UserRateThrottle, )
4228
4229    def delete(self, request, repo_id, format=None):
4230        """
4231        Unshare a library.
4232        Repo owner and system admin can perform this operation.
4233        """
4234        repo = get_repo(repo_id)
4235        if not repo:
4236            return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
4237
4238        username = request.user.username
4239        if is_org_context(request):
4240            repo_owner = seafile_api.get_org_repo_owner(repo_id)
4241        else:
4242            repo_owner = seafile_api.get_repo_owner(repo_id)
4243
4244        if not request.user.is_staff and not username == repo_owner:
4245            return api_error(status.HTTP_403_FORBIDDEN,
4246                             'You do not have permission to unshare library.')
4247
4248        share_type = request.GET.get('share_type', '')
4249        if not share_type:
4250            return api_error(status.HTTP_400_BAD_REQUEST,
4251                             'Share type is required.')
4252
4253        if share_type == 'personal':
4254            user = request.GET.get('user', '')
4255            if not user:
4256                return api_error(status.HTTP_400_BAD_REQUEST,
4257                                 'User is required.')
4258
4259            if not is_valid_username(user):
4260                return api_error(status.HTTP_400_BAD_REQUEST,
4261                                 'User is not valid')
4262
4263            remove_share(repo_id, username, user)
4264        elif share_type == 'group':
4265            group_id = request.GET.get('group_id', '')
4266            if not group_id:
4267                return api_error(status.HTTP_400_BAD_REQUEST,
4268                                 'Group ID is required.')
4269
4270            try:
4271                group_id = int(group_id)
4272            except ValueError:
4273                return api_error(status.HTTP_400_BAD_REQUEST,
4274                                 'Group ID is not valid.')
4275
4276            seafile_api.unset_group_repo(repo_id, int(group_id), username)
4277        elif share_type == 'public':
4278            if is_org_context(request):
4279                org_id = request.user.org.org_id
4280                seaserv.seafserv_threaded_rpc.unset_org_inner_pub_repo(org_id, repo_id)
4281            else:
4282                seafile_api.remove_inner_pub_repo(repo_id)
4283        else:
4284            return api_error(status.HTTP_400_BAD_REQUEST,
4285                             'Share type can only be personal or group or public.')
4286
4287        return Response('success', status=status.HTTP_200_OK)
4288
4289    def put(self, request, repo_id, format=None):
4290        """
4291        Share a repo to users/groups/public.
4292        """
4293
4294        # argument check
4295        share_type = request.GET.get('share_type')
4296        permission = request.GET.get('permission')
4297
4298        if permission not in get_available_repo_perms():
4299            error_msg = 'permission invalid.'
4300            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
4301
4302        if share_type not in ('personal', 'group', 'public'):
4303            error_msg = 'share_type invalid.'
4304            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
4305
4306        # recourse check
4307        repo = seafile_api.get_repo(repo_id)
4308        if not repo:
4309            error_msg = 'Library %s not found.' % repo_id
4310            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
4311
4312        # permission check
4313        username = request.user.username
4314        repo_owner = get_repo_owner(request, repo_id)
4315        if username != repo_owner:
4316            error_msg = 'Permission denied.'
4317            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
4318
4319        if share_type == 'personal':
4320
4321            user = request.GET.get('user')
4322            users = request.GET.get('users')
4323            if not user and not users:
4324                return api_error(status.HTTP_400_BAD_REQUEST,
4325                                 'User or users (comma separated are mandatory) are not provided')
4326            usernames = []
4327            if user:
4328                usernames += user.split(",")
4329            if users:
4330                usernames += users.split(",")
4331
4332            shared_users = []
4333            invalid_users = []
4334            notexistent_users = []
4335            notsharable_errors = []
4336
4337            for u in usernames:
4338                if not u:
4339                    continue
4340
4341                if not is_valid_username(u):
4342                    invalid_users.append(u)
4343                    continue
4344
4345                if not is_registered_user(u):
4346                    notexistent_users.append(u)
4347                    continue
4348
4349                try:
4350                    seafile_api.share_repo(repo_id, username, u, permission)
4351                    shared_users.append(u)
4352                except SearpcError as e:
4353                    logger.error(e)
4354                    notsharable_errors.append(e)
4355
4356                try:
4357                    send_perm_audit_msg('add-repo-perm',
4358                            username, u, repo_id, '/', permission)
4359                except Exception as e:
4360                    logger.error(e)
4361
4362            if invalid_users or notexistent_users or notsharable_errors:
4363                # removing already created share
4364                for s_user in shared_users:
4365                    try:
4366                        remove_share(repo_id, username, s_user)
4367                    except SearpcError as e:
4368                        # ignoring this error, go to next unsharing
4369                        continue
4370
4371            if invalid_users:
4372                return api_error(status.HTTP_400_BAD_REQUEST,
4373                                 'Some users are not valid, sharing rolled back')
4374            if notexistent_users:
4375                return api_error(status.HTTP_400_BAD_REQUEST,
4376                                 'Some users are not existent, sharing rolled back')
4377            if notsharable_errors:
4378                # show the first sharing error
4379                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
4380                                 'Internal error occurs, sharing rolled back')
4381
4382        if share_type == 'group':
4383
4384            group_id = request.GET.get('group_id')
4385            if not group_id:
4386                error_msg = 'group_id invalid.'
4387                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
4388
4389            try:
4390                group_id = int(group_id)
4391            except ValueError:
4392                return api_error(status.HTTP_400_BAD_REQUEST,
4393                                 'Group ID must be integer.')
4394
4395            group = get_group(group_id)
4396            if not group:
4397                return api_error(status.HTTP_400_BAD_REQUEST,
4398                                 'Group does not exist .')
4399            try:
4400                seafile_api.set_group_repo(repo_id,
4401                        group_id, username, permission)
4402            except SearpcError as e:
4403                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
4404                                 "Searpc Error: " + e.msg)
4405            try:
4406                send_perm_audit_msg('add-repo-perm',
4407                        username, group_id, repo_id, '/', permission)
4408            except Exception as e:
4409                logger.error(e)
4410
4411        if share_type == 'public':
4412            try:
4413                if is_org_context(request):
4414                    org_id = request.user.org.org_id
4415                    seafile_api.set_org_inner_pub_repo(org_id, repo_id, permission)
4416                else:
4417                    if not request.user.permissions.can_add_public_repo():
4418                        error_msg = 'Permission denied.'
4419                        return api_error(status.HTTP_403_FORBIDDEN, error_msg)
4420
4421                    seafile_api.add_inner_pub_repo(repo_id, permission)
4422            except Exception as e:
4423                logger.error(e)
4424                error_msg = 'Internal Server Error'
4425                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
4426
4427            try:
4428                send_perm_audit_msg('add-repo-perm',
4429                        username, 'all', repo_id, '/', permission)
4430            except Exception as e:
4431                logger.error(e)
4432
4433        return Response('success', status=status.HTTP_200_OK)
4434
4435class EventsView(APIView):
4436    authentication_classes = (TokenAuthentication, SessionAuthentication)
4437    permission_classes = (IsAuthenticated,)
4438    throttle_classes = (UserRateThrottle, )
4439
4440    def get(self, request, format=None):
4441        if not EVENTS_ENABLED:
4442            events = None
4443            return api_error(status.HTTP_404_NOT_FOUND, 'Events not enabled.')
4444
4445        start = request.GET.get('start', '')
4446
4447        if not start:
4448            start = 0
4449        else:
4450            try:
4451                start = int(start)
4452            except ValueError:
4453                return api_error(status.HTTP_400_BAD_REQUEST, 'Start id must be integer')
4454
4455        email = request.user.username
4456        events_count = 15
4457
4458        if is_org_context(request):
4459            org_id = request.user.org.org_id
4460            events, events_more_offset = get_org_user_events(org_id, email,
4461                                                             start,
4462                                                             events_count)
4463        else:
4464            events, events_more_offset = get_user_events(email, start,
4465                                                         events_count)
4466        events_more = True if len(events) == events_count else False
4467
4468        l = []
4469        for e in events:
4470            d = dict(etype=e.etype)
4471            l.append(d)
4472            if e.etype == 'repo-update':
4473                d['author'] = e.commit.creator_name
4474                d['time'] = e.commit.ctime
4475                d['desc'] = e.commit.desc
4476                d['repo_id'] = e.repo.id
4477                d['repo_name'] = e.repo.name
4478                d['commit_id'] = e.commit.id
4479                d['converted_cmmt_desc'] = translate_commit_desc_escape(convert_cmmt_desc_link(e.commit))
4480                d['more_files'] = e.commit.more_files
4481                d['repo_encrypted'] = e.repo.encrypted
4482            elif e.etype == 'clean-up-repo-trash':
4483                d['repo_id'] = e.repo_id
4484                d['author'] = e.username
4485                d['time'] = datetime_to_timestamp(e.timestamp)
4486                d['days'] = e.days
4487                d['repo_name'] = e.repo_name
4488                d['etype'] = e.etype
4489            else:
4490                d['repo_id'] = e.repo_id
4491                d['repo_name'] = e.repo_name
4492                if e.etype == 'repo-create':
4493                    d['author'] = e.creator
4494                else:
4495                    d['author'] = e.repo_owner
4496
4497                d['time'] = datetime_to_timestamp(e.timestamp)
4498
4499            size = request.GET.get('size', 36)
4500            url, is_default, date_uploaded = api_avatar_url(d['author'], size)
4501            d['nick'] = email2nickname(d['author'])
4502            d['name'] = email2nickname(d['author'])
4503            d['avatar'] = avatar(d['author'], size)
4504            d['avatar_url'] = url
4505            d['time_relative'] = translate_seahub_time(utc_to_local(e.timestamp))
4506            d['date'] = utc_to_local(e.timestamp).strftime("%Y-%m-%d")
4507
4508        ret = {
4509            'events': l,
4510            'more': events_more,
4511            'more_offset': events_more_offset,
4512            }
4513        return Response(ret)
4514
4515class UnseenMessagesCountView(APIView):
4516    authentication_classes = (TokenAuthentication, )
4517    permission_classes = (IsAuthenticated,)
4518    throttle_classes = (UserRateThrottle, )
4519
4520    def get(self, request, format=None):
4521        username = request.user.username
4522        ret = { 'count' : UserNotification.objects.count_unseen_user_notifications(username)
4523                }
4524        return Response(ret)
4525
4526########## Groups related
4527class Groups(APIView):
4528    authentication_classes = (TokenAuthentication, SessionAuthentication)
4529    permission_classes = (IsAuthenticated,)
4530    throttle_classes = (UserRateThrottle, )
4531
4532    def get(self, request, format=None):
4533
4534        size = request.GET.get('size', 36)
4535        limit = int(request.GET.get('limit', 8))
4536        with_msg = request.GET.get('with_msg', 'true')
4537
4538        # To not broken the old API, we need to make with_msg default
4539        if with_msg == 'true':
4540            group_json, replynum = get_groups(request.user.username)
4541            res = {"groups": group_json, "replynum": replynum}
4542            return Response(res)
4543        else:
4544            groups_json = []
4545            joined_groups = ccnet_api.get_groups(request.user.username)
4546
4547            for g in joined_groups:
4548
4549                if limit <= 0:
4550                    break;
4551
4552                group = {
4553                    "id": g.id,
4554                    "name": g.group_name,
4555                    "creator": g.creator_name,
4556                    "ctime": g.timestamp,
4557                    "avatar": grp_avatar(g.id, int(size)),
4558                }
4559                groups_json.append(group)
4560                limit = limit - 1
4561
4562            return Response(groups_json)
4563
4564    def put(self, request, format=None):
4565        # modified slightly from groups/views.py::group_list
4566        """
4567        Add a new group.
4568        """
4569        result = {}
4570        content_type = 'application/json; charset=utf-8'
4571        username = request.user.username
4572
4573        if not request.user.permissions.can_add_group():
4574            return api_error(status.HTTP_403_FORBIDDEN,
4575                             'You do not have permission to create group.')
4576
4577        # check plan
4578        num_of_groups = getattr(request.user, 'num_of_groups', -1)
4579        if num_of_groups > 0:
4580            current_groups = len(ccnet_api.get_groups(username))
4581            if current_groups > num_of_groups:
4582                result['error'] = 'You can only create %d groups.' % num_of_groups
4583                return HttpResponse(json.dumps(result), status=500,
4584                                    content_type=content_type)
4585
4586        group_name = request.data.get('group_name', None)
4587        group_name = group_name.strip()
4588        if not validate_group_name(group_name):
4589            result['error'] = _('Name can only contain letters, numbers, spaces, hyphen, dot, single quote, brackets or underscore.')
4590            return HttpResponse(json.dumps(result), status=403,
4591                                content_type=content_type)
4592
4593        # Check whether group name is duplicated.
4594        if request.cloud_mode:
4595            checked_groups = ccnet_api.get_groups(username)
4596        else:
4597            checked_groups = get_personal_groups(-1, -1)
4598        for g in checked_groups:
4599            if g.group_name == group_name:
4600                result['error'] = 'There is already a group with that name.'
4601                return HttpResponse(json.dumps(result), status=400,
4602                                    content_type=content_type)
4603
4604        # Group name is valid, create that group.
4605        try:
4606            group_id = ccnet_api.create_group(group_name, username)
4607            return HttpResponse(json.dumps({'success': True, 'group_id': group_id}),
4608                                content_type=content_type)
4609        except SearpcError as e:
4610            result['error'] = e.msg
4611            return HttpResponse(json.dumps(result), status=500,
4612                                content_type=content_type)
4613
4614    def delete(self, request, group_id, format=None):
4615        try:
4616            group_id = int(group_id)
4617        except ValueError:
4618            return api_error(status.HTTP_400_BAD_REQUEST, 'Bad group id format')
4619
4620        group = seaserv.get_group(group_id)
4621        if not group:
4622            return api_error(status.HTTP_404_NOT_FOUND, 'Group not found')
4623
4624        # permission check
4625        username = request.user.username
4626        if not seaserv.check_group_staff(group_id, username):
4627            return api_error(status.HTTP_403_FORBIDDEN, 'You do not have permission to delete group')
4628
4629        # delete group
4630        if is_org_context(request):
4631            org_id = request.user.org.org_id
4632        else:
4633            org_id = None
4634
4635        try:
4636            remove_group_common(group.id, username, org_id=org_id)
4637        except SearpcError as e:
4638            logger.error(e)
4639            return api_error(HTTP_520_OPERATION_FAILED,
4640                             'Failed to remove group.')
4641
4642        return Response('success', status=status.HTTP_200_OK)
4643
4644    def post(self, request, group_id, format=None):
4645        group = seaserv.get_group(group_id)
4646        if not group:
4647            return api_error(status.HTTP_404_NOT_FOUND, 'Group not found')
4648
4649        # permission check
4650        username = request.user.username
4651        if not seaserv.check_group_staff(group.id, username):
4652            return api_error(status.HTTP_403_FORBIDDEN, 'You do not have permission to rename group')
4653
4654        operation = request.POST.get('operation', '')
4655        if operation.lower() == 'rename':
4656            newname = request.POST.get('newname', '')
4657            if not newname:
4658                return api_error(status.HTTP_400_BAD_REQUEST,
4659                                 'New name is missing')
4660
4661            try:
4662                rename_group_with_new_name(request, group.id, newname)
4663            except BadGroupNameError:
4664                return api_error(status.HTTP_400_BAD_REQUEST,
4665                                 'Group name is not valid.')
4666            except ConflictGroupNameError:
4667                return api_error(status.HTTP_400_BAD_REQUEST,
4668                                 'There is already a group with that name.')
4669
4670            return Response('success', status=status.HTTP_200_OK)
4671        else:
4672            return api_error(status.HTTP_400_BAD_REQUEST,
4673                             "Operation can only be rename.")
4674
4675class GroupMembers(APIView):
4676    authentication_classes = (TokenAuthentication, SessionAuthentication)
4677    permission_classes = (IsAuthenticated,)
4678    throttle_classes = (UserRateThrottle,)
4679
4680    def put(self, request, group_id, format=None):
4681        """
4682        Add group members.
4683        """
4684        try:
4685            group_id_int = int(group_id)
4686        except ValueError:
4687            return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid group ID')
4688
4689        group = get_group(group_id_int)
4690        if not group:
4691            return api_error(status.HTTP_404_NOT_FOUND, 'Group not found')
4692
4693        if not is_group_staff(group, request.user):
4694            return api_error(status.HTTP_403_FORBIDDEN, 'Only administrators can add group members')
4695
4696        user_name = request.data.get('user_name', None)
4697        if not is_registered_user(user_name):
4698            return api_error(status.HTTP_400_BAD_REQUEST, 'Not a valid user')
4699
4700        try:
4701            ccnet_threaded_rpc.group_add_member(group.id, request.user.username, user_name)
4702        except SearpcError as e:
4703            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Unable to add user to group')
4704
4705        return HttpResponse(json.dumps({'success': True}), status=200, content_type=json_content_type)
4706
4707    def delete(self, request, group_id, format=None):
4708        """
4709        Delete group members.
4710        """
4711        try:
4712            group_id_int = int(group_id)
4713        except ValueError:
4714            return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid group ID')
4715
4716        group = get_group(group_id_int)
4717        if not group:
4718            return api_error(status.HTTP_404_NOT_FOUND, 'Group not found')
4719
4720        if not is_group_staff(group, request.user):
4721            return api_error(status.HTTP_403_FORBIDDEN, 'Only administrators can remove group members')
4722
4723        user_name = request.data.get('user_name', None)
4724
4725        try:
4726            ccnet_threaded_rpc.group_remove_member(group.id, request.user.username, user_name)
4727        except SearpcError as e:
4728            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Unable to add user to group')
4729
4730        return HttpResponse(json.dumps({'success': True}), status=200, content_type=json_content_type)
4731
4732class GroupRepos(APIView):
4733    authentication_classes = (TokenAuthentication, SessionAuthentication)
4734    permission_classes = (IsAuthenticated,)
4735    throttle_classes = (UserRateThrottle, )
4736
4737    @api_group_check
4738    def post(self, request, group, format=None):
4739        # add group repo
4740        username = request.user.username
4741        repo_name = request.data.get("name", None)
4742        repo_desc = request.data.get("desc", '')
4743        passwd = request.data.get("passwd", None)
4744
4745        # to avoid 'Bad magic' error when create repo, passwd should be 'None'
4746        # not an empty string when create unencrypted repo
4747        if not passwd:
4748            passwd = None
4749
4750        if (passwd is not None) and (not config.ENABLE_ENCRYPTED_LIBRARY):
4751            return api_error(status.HTTP_403_FORBIDDEN,
4752                             'NOT allow to create encrypted library.')
4753
4754        permission = request.data.get("permission", 'r')
4755        if permission not in get_available_repo_perms():
4756            return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid permission')
4757
4758        org_id = -1
4759        if is_org_context(request):
4760            org_id = request.user.org.org_id
4761            repo_id = seafile_api.create_org_repo(repo_name, repo_desc,
4762                                                  username, org_id, passwd,
4763                                                  enc_version=settings.ENCRYPTED_LIBRARY_VERSION)
4764            repo = seafile_api.get_repo(repo_id)
4765            seafile_api.add_org_group_repo(repo_id, org_id, group.id,
4766                                           username, permission)
4767        else:
4768            if is_pro_version() and ENABLE_STORAGE_CLASSES:
4769
4770                if STORAGE_CLASS_MAPPING_POLICY in ('USER_SELECT',
4771                        'ROLE_BASED'):
4772
4773                    storages = get_library_storages(request)
4774                    storage_id = request.data.get("storage_id", None)
4775                    if storage_id and storage_id not in [s['storage_id'] for s in storages]:
4776                        error_msg = 'storage_id invalid.'
4777                        return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
4778
4779                    repo_id = seafile_api.create_repo(repo_name,
4780                            repo_desc, username, passwd, storage_id,
4781                            enc_version=settings.ENCRYPTED_LIBRARY_VERSION)
4782                else:
4783                    # STORAGE_CLASS_MAPPING_POLICY == 'REPO_ID_MAPPING'
4784                    repo_id = seafile_api.create_repo(repo_name,
4785                            repo_desc, username, passwd,
4786                            enc_version=settings.ENCRYPTED_LIBRARY_VERSION)
4787            else:
4788                repo_id = seafile_api.create_repo(repo_name,
4789                        repo_desc, username, passwd,
4790                        enc_version=settings.ENCRYPTED_LIBRARY_VERSION)
4791
4792            repo = seafile_api.get_repo(repo_id)
4793            seafile_api.set_group_repo(repo.id, group.id, username, permission)
4794
4795        library_template = request.data.get("library_template", '')
4796        repo_created.send(sender=None,
4797                          org_id=org_id,
4798                          creator=username,
4799                          repo_id=repo_id,
4800                          repo_name=repo_name,
4801                          library_template=library_template)
4802        group_repo = {
4803            "id": repo.id,
4804            "name": repo.name,
4805            "desc": repo.desc,
4806            "size": repo.size,
4807            "size_formatted": filesizeformat(repo.size),
4808            "mtime": repo.last_modified,
4809            "mtime_relative": translate_seahub_time(repo.last_modified),
4810            "encrypted": repo.encrypted,
4811            "permission": permission,
4812            "owner": username,
4813            "owner_nickname": email2nickname(username),
4814            "owner_name": email2nickname(username),
4815            "share_from_me": True,
4816            "modifier_email": repo.last_modifier,
4817            "modifier_contact_email": email2contact_email(repo.last_modifier),
4818            "modifier_name": email2nickname(repo.last_modifier),
4819        }
4820
4821        return Response(group_repo, status=200)
4822
4823    @api_group_check
4824    def get(self, request, group, format=None):
4825        username = request.user.username
4826
4827        if group.is_pub:
4828            if not request.user.is_staff and not is_group_member(group.id, username):
4829                return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
4830
4831        if is_org_context(request):
4832            org_id = request.user.org.org_id
4833            repos = seafile_api.get_org_group_repos(org_id, group.id)
4834        else:
4835            repos = seafile_api.get_repos_by_group(group.id)
4836
4837        repos.sort(key=lambda x: x.last_modified, reverse=True)
4838        group.is_staff = is_group_staff(group, request.user)
4839
4840        # Use dict to reduce memcache fetch cost in large for-loop.
4841        contact_email_dict = {}
4842        nickname_dict = {}
4843        owner_set = {x.user for x in repos}
4844        modifiers_set = {x.modifier for x in repos}
4845        for e in owner_set | modifiers_set:
4846            if e not in contact_email_dict:
4847                contact_email_dict[e] = email2contact_email(e)
4848            if e not in nickname_dict:
4849                nickname_dict[e] = email2nickname(e)
4850
4851        # Get repos that is admin permission in group.
4852        admin_repos = ExtraGroupsSharePermission.objects.\
4853                get_repos_with_admin_permission(group.id)
4854        repos_json = []
4855        for r in repos:
4856
4857            group_name_of_address_book_library = ''
4858            if '@seafile_group' in r.user:
4859                group_id_of_address_book_library = get_group_id_by_repo_owner(r.user)
4860                group_name_of_address_book_library = group_id_to_name(group_id_of_address_book_library)
4861
4862            repo = {
4863                "id": r.id,
4864                "name": r.name,
4865                "size": r.size,
4866                "size_formatted": filesizeformat(r.size),
4867                "mtime": r.last_modified,
4868                "mtime_relative": translate_seahub_time(r.last_modified),
4869                "encrypted": r.encrypted,
4870                "permission": r.permission,
4871                "owner": r.user,
4872                "owner_nickname": nickname_dict.get(r.user, ''),
4873                "owner_name": nickname_dict.get(r.user, ''),
4874                "share_from_me": True if username == r.user else False,
4875                "modifier_email": r.last_modifier,
4876                "modifier_contact_email": contact_email_dict.get(r.last_modifier, ''),
4877                "modifier_name": nickname_dict.get(r.last_modifier, ''),
4878                "is_admin": r.id in admin_repos,
4879                "group_name": group_name_of_address_book_library,
4880            }
4881            repos_json.append(repo)
4882
4883        req_from = request.GET.get('from', "")
4884        if req_from == 'web':
4885            return Response({"is_staff": group.is_staff, "repos": repos_json})
4886        else:
4887            return Response(repos_json)
4888
4889class GroupRepo(APIView):
4890    authentication_classes = (TokenAuthentication, SessionAuthentication)
4891    permission_classes = (IsAuthenticated,)
4892    throttle_classes = (UserRateThrottle, )
4893
4894    @api_group_check
4895    def delete(self, request, group, repo_id, format=None):
4896        username = request.user.username
4897        group_id = group.id
4898
4899        # only admin or owner can delete share record.
4900        repo_owner = get_repo_owner(request, repo_id)
4901        if not group.is_staff and repo_owner != username and not is_repo_admin(username, repo_id):
4902            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
4903
4904        is_org = seaserv.is_org_group(group_id)
4905        repo = seafile_api.get_group_shared_repo_by_path(repo_id, None, group_id, is_org)
4906        permission = check_group_share_in_permission(repo_id, group_id, is_org)
4907
4908        if is_org:
4909            org_id = seaserv.get_org_id_by_group(group_id)
4910            seaserv.del_org_group_repo(repo_id, org_id, group_id)
4911        else:
4912            seafile_api.unset_group_repo(repo_id, group_id, username)
4913
4914        # delete extra share permission
4915        ExtraGroupsSharePermission.objects.delete_share_permission(repo_id, group_id)
4916        if repo.is_virtual:
4917            send_perm_audit_msg('delete-repo-perm', username, group_id,
4918                                repo.origin_repo_id, repo.origin_path, permission)
4919        else:
4920            send_perm_audit_msg('delete-repo-perm', username, group_id,
4921                                repo_id, '/', permission)
4922        return HttpResponse(json.dumps({'success': True}), status=200,
4923                            content_type=json_content_type)
4924
4925class UserAvatarView(APIView):
4926    authentication_classes = (TokenAuthentication, SessionAuthentication)
4927    permission_classes = (IsAuthenticated,)
4928    throttle_classes = (UserRateThrottle, )
4929
4930    def get(self, request, user, size, format=None):
4931        url, is_default, date_uploaded = api_avatar_url(user, int(size))
4932        ret = {
4933            "url": url,
4934            "is_default": is_default,
4935            "mtime": get_timestamp(date_uploaded) }
4936        return Response(ret)
4937
4938class GroupAvatarView(APIView):
4939    authentication_classes = (TokenAuthentication, )
4940    permission_classes = (IsAuthenticated,)
4941    throttle_classes = (UserRateThrottle, )
4942
4943    def get(self, request, group_id, size, format=None):
4944        url, is_default, date_uploaded = api_grp_avatar_url(group_id, int(size))
4945        ret = {
4946            "url": request.build_absolute_uri(url),
4947            "is_default": is_default,
4948            "mtime": get_timestamp(date_uploaded)}
4949        return Response(ret)
4950
4951class RepoHistoryChange(APIView):
4952    authentication_classes = (TokenAuthentication, )
4953    permission_classes = (IsAuthenticated,)
4954    throttle_classes = (UserRateThrottle, )
4955
4956    def get(self, request, repo_id, format=None):
4957        repo = get_repo(repo_id)
4958        if not repo:
4959            return HttpResponse(json.dumps({"err": 'Library does not exist'}),
4960                                status=400,
4961                                content_type=json_content_type)
4962
4963        if not check_folder_permission(request, repo_id, '/'):
4964            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
4965
4966        commit_id = request.GET.get('commit_id', '')
4967        if not commit_id:
4968            return HttpResponse(json.dumps({"err": 'Invalid argument'}),
4969                                status=400,
4970                                content_type=json_content_type)
4971
4972        details = get_diff_details(repo_id, '', commit_id)
4973
4974        return HttpResponse(json.dumps(details),
4975                            content_type=json_content_type)
4976
4977
4978# based on views/file.py::office_convert_query_status
4979class OfficeConvertQueryStatus(APIView):
4980    authentication_classes = (TokenAuthentication, )
4981    permission_classes = (IsAuthenticated, )
4982    throttle_classes = (UserRateThrottle, )
4983
4984    def get(self, request, format=None):
4985        if not HAS_OFFICE_CONVERTER:
4986            return api_error(status.HTTP_404_NOT_FOUND, 'Office converter not enabled.')
4987
4988        content_type = 'application/json; charset=utf-8'
4989
4990        ret = {'success': False}
4991
4992        file_id = request.GET.get('file_id', '')
4993        if len(file_id) != 40:
4994            ret['error'] = 'invalid param'
4995        else:
4996            try:
4997                d = query_office_convert_status(file_id)
4998                if d.error:
4999                    ret['error'] = d.error
5000                else:
5001                    ret['success'] = True
5002                    ret['status'] = d.status
5003            except Exception as e:
5004                logging.exception('failed to call query_office_convert_status')
5005                ret['error'] = str(e)
5006
5007        return HttpResponse(json.dumps(ret), content_type=content_type)
5008
5009# based on views/file.py::view_file and views/file.py::handle_document
5010class OfficeGenerateView(APIView):
5011    authentication_classes = (TokenAuthentication, )
5012    permission_classes = (IsAuthenticated, )
5013    throttle_classes = (UserRateThrottle, )
5014
5015    def get(self, request, repo_id, format=None):
5016        username = request.user.username
5017        # check arguments
5018        repo = get_repo(repo_id)
5019        if not repo:
5020            return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
5021
5022
5023        path = request.GET.get('p', '/').rstrip('/')
5024        commit_id = request.GET.get('commit_id', None)
5025
5026        if commit_id:
5027            try:
5028                obj_id = seafserv_threaded_rpc.get_file_id_by_commit_and_path(
5029                    repo.id, commit_id, path)
5030            except:
5031                return api_error(status.HTTP_404_NOT_FOUND, 'Revision not found.')
5032        else:
5033            try:
5034                obj_id = seafile_api.get_file_id_by_path(repo_id, path)
5035            except:
5036                return api_error(status.HTTP_404_NOT_FOUND, 'File not found.')
5037
5038        if not obj_id:
5039            return api_error(status.HTTP_404_NOT_FOUND, 'File not found.')
5040
5041        # Check whether user has permission to view file and get file raw path,
5042        # render error page if permission deny.
5043        raw_path, inner_path, user_perm = get_file_view_path_and_perm(request,
5044                                                                      repo_id,
5045                                                                      obj_id, path)
5046
5047        if not user_perm:
5048            return api_error(status.HTTP_403_FORBIDDEN, 'You do not have permission to view this file.')
5049
5050        u_filename = os.path.basename(path)
5051        filetype, fileext = get_file_type_and_ext(u_filename)
5052        if filetype != DOCUMENT:
5053            return api_error(status.HTTP_400_BAD_REQUEST, 'File is not a convertable document')
5054
5055        ret_dict = {}
5056        if HAS_OFFICE_CONVERTER:
5057            err = prepare_converted_html(inner_path, obj_id, fileext, ret_dict)
5058            # populate return value dict
5059            ret_dict['err'] = err
5060            ret_dict['obj_id'] = obj_id
5061        else:
5062            ret_dict['filetype'] = 'Unknown'
5063
5064        return HttpResponse(json.dumps(ret_dict), status=200, content_type=json_content_type)
5065
5066class ThumbnailView(APIView):
5067    authentication_classes = (TokenAuthentication, SessionAuthentication)
5068    permission_classes = (IsAuthenticated,)
5069    throttle_classes = (UserRateThrottle, )
5070
5071    def get(self, request, repo_id):
5072
5073        repo = get_repo(repo_id)
5074        if not repo:
5075            return api_error(status.HTTP_404_NOT_FOUND, 'Library not found.')
5076
5077        size = request.GET.get('size', None)
5078        if size is None:
5079            return api_error(status.HTTP_400_BAD_REQUEST, 'Size is missing.')
5080
5081        try:
5082            size = int(size)
5083        except ValueError as e:
5084            logger.error(e)
5085            return api_error(status.HTTP_400_BAD_REQUEST, 'Invalid size.')
5086
5087        path = request.GET.get('p', None)
5088        obj_id = get_file_id_by_path(repo_id, path)
5089        if path is None or obj_id is None:
5090            return api_error(status.HTTP_400_BAD_REQUEST, 'Wrong path.')
5091
5092        if repo.encrypted or not ENABLE_THUMBNAIL or \
5093            check_folder_permission(request, repo_id, path) is None:
5094            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')
5095
5096        success, status_code = generate_thumbnail(request, repo_id, size, path)
5097        if success:
5098            thumbnail_dir = os.path.join(THUMBNAIL_ROOT, str(size))
5099            thumbnail_file = os.path.join(thumbnail_dir, obj_id)
5100            try:
5101                with open(thumbnail_file, 'rb') as f:
5102                    thumbnail = f.read()
5103                return HttpResponse(thumbnail, 'image/' + THUMBNAIL_EXTENSION)
5104            except IOError as e:
5105                logger.error(e)
5106                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Failed to get thumbnail.')
5107        else:
5108            if status_code == 400:
5109                return api_error(status.HTTP_400_BAD_REQUEST, "Invalid argument")
5110            if status_code == 403:
5111                return api_error(status.HTTP_403_FORBIDDEN, 'Forbidden')
5112            if status_code == 500:
5113                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Failed to generate thumbnail.')
5114
5115_REPO_ID_PATTERN = re.compile(r'[-0-9a-f]{36}')
5116
5117class RepoTokensView(APIView):
5118    authentication_classes = (TokenAuthentication,)
5119    permission_classes = (IsAuthenticated,)
5120    throttle_classes = (UserRateThrottle,)
5121
5122    @json_response
5123    def get(self, request, format=None):
5124        repos_id_str = request.GET.get('repos', None)
5125        if not repos_id_str:
5126            return api_error(status.HTTP_400_BAD_REQUEST, "You must specify libaries ids")
5127
5128        repos_id = [repo_id for repo_id in repos_id_str.split(',') if repo_id]
5129        if any([not _REPO_ID_PATTERN.match(repo_id) for repo_id in repos_id]):
5130            return api_error(status.HTTP_400_BAD_REQUEST, "Libraries ids are invalid")
5131
5132        tokens = {}
5133        for repo_id in repos_id:
5134            repo = seafile_api.get_repo(repo_id)
5135            if not repo:
5136                continue
5137
5138            if not check_folder_permission(request, repo.id, '/'):
5139                continue
5140
5141            tokens[repo_id] = seafile_api.generate_repo_token(repo_id, request.user.username)
5142
5143        return tokens
5144
5145class OrganizationView(APIView):
5146    authentication_classes = (TokenAuthentication, )
5147    permission_classes = (IsAdminUser, )
5148    throttle_classes = (UserRateThrottle, )
5149
5150    def post(self, request, format=None):
5151
5152        if not CLOUD_MODE or not MULTI_TENANCY:
5153            error_msg = 'Feature is not enabled.'
5154            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
5155
5156        username = request.POST.get('username', None)
5157        password = request.POST.get('password', None)
5158        org_name = request.POST.get('org_name', None)
5159        prefix = request.POST.get('prefix', None)
5160        quota = request.POST.get('quota', None)
5161        member_limit = request.POST.get('member_limit', ORG_MEMBER_QUOTA_DEFAULT)
5162
5163        if not org_name or not username or not password or \
5164                not prefix or not quota or not member_limit:
5165            return api_error(status.HTTP_400_BAD_REQUEST, "Missing argument")
5166
5167        if not is_valid_username(username):
5168            return api_error(status.HTTP_400_BAD_REQUEST, "Email is not valid")
5169
5170        try:
5171            quota_mb = int(quota)
5172        except ValueError as e:
5173            logger.error(e)
5174            return api_error(status.HTTP_400_BAD_REQUEST, "Quota is not valid")
5175
5176        try:
5177            User.objects.get(email = username)
5178            user_exist = True
5179        except User.DoesNotExist:
5180            user_exist = False
5181
5182        if user_exist:
5183            return api_error(status.HTTP_400_BAD_REQUEST, "A user with this email already exists")
5184
5185        slug_re = re.compile(r'^[-a-zA-Z0-9_]+$')
5186        if not slug_re.match(prefix):
5187            return api_error(status.HTTP_400_BAD_REQUEST, "URL prefix can only be letters(a-z), numbers, and the underscore character")
5188
5189        if ccnet_threaded_rpc.get_org_by_url_prefix(prefix):
5190            return api_error(status.HTTP_400_BAD_REQUEST, "An organization with this prefix already exists")
5191
5192        try:
5193            User.objects.create_user(username, password, is_staff=False, is_active=True)
5194            create_org(org_name, prefix, username)
5195
5196            org = ccnet_threaded_rpc.get_org_by_url_prefix(prefix)
5197            org_id = org.org_id
5198
5199            # set member limit
5200            from seahub_extra.organizations.models import OrgMemberQuota
5201            OrgMemberQuota.objects.set_quota(org_id, member_limit)
5202
5203            # set quota
5204            quota = quota_mb * get_file_size_unit('MB')
5205            seafserv_threaded_rpc.set_org_quota(org_id, quota)
5206
5207            org_info = {}
5208            org_info['org_id'] = org_id
5209            org_info['org_name'] = org.org_name
5210            org_info['ctime'] = timestamp_to_isoformat_timestr(org.ctime)
5211            org_info['org_url_prefix'] = org.url_prefix
5212
5213            creator = org.creator
5214            org_info['creator_email'] = creator
5215            org_info['creator_name'] = email2nickname(creator)
5216            org_info['creator_contact_email'] = email2contact_email(creator)
5217
5218            return Response(org_info, status=status.HTTP_201_CREATED)
5219        except Exception as e:
5220            logger.error(e)
5221            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, "Internal Server Error")
5222
5223class RepoDownloadSharedLinks(APIView):
5224    authentication_classes = (TokenAuthentication, SessionAuthentication)
5225    permission_classes = (IsAuthenticated, )
5226    throttle_classes = (UserRateThrottle, )
5227
5228    def get(self, request, repo_id, format=None):
5229        repo = get_repo(repo_id)
5230        if not repo:
5231            error_msg = 'Library %s not found.' % repo_id
5232            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5233
5234        org_id = None
5235        if is_org_context(request):
5236            org_id = request.user.org.org_id
5237
5238        # check permission
5239        if org_id:
5240            repo_owner = seafile_api.get_org_repo_owner(repo_id)
5241        else:
5242            repo_owner = seafile_api.get_repo_owner(repo_id)
5243
5244        if request.user.username != repo_owner or repo.is_virtual:
5245            error_msg = 'Permission denied.'
5246            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
5247
5248        shared_links = []
5249        fileshares = FileShare.objects.filter(repo_id=repo_id)
5250        for fs in fileshares:
5251            size = None
5252            shared_link = {}
5253            if fs.is_file_share_link():
5254                path = fs.path.rstrip('/') # Normalize file path
5255                if seafile_api.get_file_id_by_path(repo.id, fs.path) is None:
5256                    continue
5257
5258                obj_id = seafile_api.get_file_id_by_path(repo_id, path)
5259                size = seafile_api.get_file_size(repo.store_id, repo.version, obj_id)
5260            else:
5261                path = fs.path
5262                if path[-1] != '/': # Normalize dir path
5263                    path += '/'
5264
5265                if seafile_api.get_dir_id_by_path(repo.id, fs.path) is None:
5266                    continue
5267
5268            shared_link['create_by'] = fs.username
5269            shared_link['creator_name'] = email2nickname(fs.username)
5270            shared_link['create_time'] = datetime_to_isoformat_timestr(fs.ctime)
5271            shared_link['token'] = fs.token
5272            shared_link['path'] = path
5273            shared_link['name'] = os.path.basename(path.rstrip('/')) if path != '/' else '/'
5274            shared_link['view_count'] = fs.view_cnt
5275            shared_link['share_type'] = fs.s_type
5276            shared_link['size'] = size if size else ''
5277            shared_links.append(shared_link)
5278
5279        return Response(shared_links)
5280
5281class RepoDownloadSharedLink(APIView):
5282    authentication_classes = (TokenAuthentication, SessionAuthentication)
5283    permission_classes = (IsAuthenticated, )
5284    throttle_classes = (UserRateThrottle, )
5285
5286    def delete(self, request, repo_id, token, format=None):
5287        repo = get_repo(repo_id)
5288        if not repo:
5289            error_msg = 'Library %s not found.' % repo_id
5290            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5291
5292        org_id = None
5293        if is_org_context(request):
5294            org_id = request.user.org.org_id
5295
5296        # check permission
5297        if org_id:
5298            repo_owner = seafile_api.get_org_repo_owner(repo_id)
5299        else:
5300            repo_owner = seafile_api.get_repo_owner(repo_id)
5301
5302        if request.user.username != repo_owner or repo.is_virtual:
5303            error_msg = 'Permission denied.'
5304            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
5305
5306        try:
5307            link = FileShare.objects.get(token=token)
5308        except FileShare.DoesNotExist:
5309            error_msg = 'Link %s not found.' % token
5310            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5311
5312        link.delete()
5313        result = {'success': True}
5314        return Response(result)
5315
5316class RepoUploadSharedLinks(APIView):
5317    authentication_classes = (TokenAuthentication, SessionAuthentication)
5318    permission_classes = (IsAuthenticated, )
5319    throttle_classes = (UserRateThrottle, )
5320
5321    def get(self, request, repo_id, format=None):
5322        repo = get_repo(repo_id)
5323        if not repo:
5324            error_msg = 'Library %s not found.' % repo_id
5325            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5326
5327        org_id = None
5328        if is_org_context(request):
5329            org_id = request.user.org.org_id
5330
5331        # check permission
5332        if org_id:
5333            repo_owner = seafile_api.get_org_repo_owner(repo_id)
5334        else:
5335            repo_owner = seafile_api.get_repo_owner(repo_id)
5336
5337        if request.user.username != repo_owner or repo.is_virtual:
5338            error_msg = 'Permission denied.'
5339            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
5340
5341        shared_links = []
5342        fileshares = UploadLinkShare.objects.filter(repo_id=repo_id)
5343        for fs in fileshares:
5344            shared_link = {}
5345            path = fs.path
5346            if path[-1] != '/': # Normalize dir path
5347                path += '/'
5348
5349            if seafile_api.get_dir_id_by_path(repo.id, fs.path) is None:
5350                continue
5351
5352            shared_link['create_by'] = fs.username
5353            shared_link['creator_name'] = email2nickname(fs.username)
5354            shared_link['create_time'] = datetime_to_isoformat_timestr(fs.ctime)
5355            shared_link['token'] = fs.token
5356            shared_link['path'] = path
5357            shared_link['name'] = os.path.basename(path.rstrip('/')) if path != '/' else '/'
5358            shared_link['view_count'] = fs.view_cnt
5359            shared_links.append(shared_link)
5360
5361        return Response(shared_links)
5362
5363class RepoUploadSharedLink(APIView):
5364    authentication_classes = (TokenAuthentication, SessionAuthentication)
5365    permission_classes = (IsAuthenticated, )
5366    throttle_classes = (UserRateThrottle, )
5367
5368    def delete(self, request, repo_id, token, format=None):
5369        repo = get_repo(repo_id)
5370        if not repo:
5371            error_msg = 'Library %s not found.' % repo_id
5372            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5373
5374        org_id = None
5375        if is_org_context(request):
5376            org_id = request.user.org.org_id
5377
5378        # check permission
5379        if org_id:
5380            repo_owner = seafile_api.get_org_repo_owner(repo_id)
5381        else:
5382            repo_owner = seafile_api.get_repo_owner(repo_id)
5383
5384        if request.user.username != repo_owner or repo.is_virtual:
5385            error_msg = 'Permission denied.'
5386            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
5387
5388        try:
5389            link = UploadLinkShare.objects.get(token=token)
5390        except FileShare.DoesNotExist:
5391            error_msg = 'Link %s not found.' % token
5392            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5393
5394        link.delete()
5395        result = {'success': True}
5396        return Response(result)
5397
5398class RepoUserFolderPerm(APIView):
5399    authentication_classes = (TokenAuthentication, SessionAuthentication)
5400    permission_classes = (IsAuthenticated,)
5401    throttle_classes = (UserRateThrottle,)
5402
5403    def _get_user_folder_perm_info(self, email, repo_id, path, perm):
5404        result = {}
5405        if email and repo_id and path and perm:
5406            result['repo_id'] = repo_id
5407            result['user_email'] = email
5408            result['user_name'] = email2nickname(email)
5409            result['folder_path'] = path
5410            result['folder_name'] = path if path == '/' else os.path.basename(path.rstrip('/'))
5411            result['permission'] = perm
5412
5413        return result
5414
5415    def get(self, request, repo_id, format=None):
5416        """ List repo user folder perms (by folder_path).
5417
5418        Permission checking:
5419        1. ( repo owner | admin ) & pro edition & enable folder perm.
5420        """
5421
5422        # resource check
5423        repo = seafile_api.get_repo(repo_id)
5424        if not repo:
5425            error_msg = 'Library %s not found.' % repo_id
5426            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5427
5428        # permission check
5429        if is_org_context(request):
5430            repo_owner = seafile_api.get_org_repo_owner(repo_id)
5431        else:
5432            repo_owner = seafile_api.get_repo_owner(repo_id)
5433
5434        username = request.user.username
5435        if not (is_pro_version() and can_set_folder_perm_by_user(username, repo, repo_owner)):
5436            error_msg = 'Permission denied.'
5437            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
5438
5439        # get perm list
5440        results = []
5441        path = request.GET.get('folder_path', None)
5442        folder_perms = seafile_api.list_folder_user_perm_by_repo(repo_id)
5443        for perm in folder_perms:
5444            result = {}
5445            if path:
5446                if path == perm.path:
5447                    result = self._get_user_folder_perm_info(
5448                            perm.user, perm.repo_id, perm.path, perm.permission)
5449            else:
5450                result = self._get_user_folder_perm_info(
5451                        perm.user, perm.repo_id, perm.path, perm.permission)
5452
5453            if result:
5454                results.append(result)
5455
5456        return Response(results)
5457
5458    def post(self, request, repo_id, format=None):
5459        """ Add repo user folder perm.
5460
5461        Permission checking:
5462        1. ( repo owner | admin ) & pro edition & enable folder perm.
5463        """
5464
5465        # argument check
5466        path = request.data.get('folder_path', None)
5467        if not path:
5468            error_msg = 'folder_path invalid.'
5469            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5470
5471        perm = request.data.get('permission', None)
5472        if not perm or perm not in get_available_repo_perms():
5473            error_msg = 'permission invalid.'
5474            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5475
5476        # resource check
5477        repo = seafile_api.get_repo(repo_id)
5478        if not repo:
5479            error_msg = 'Library %s not found.' % repo_id
5480            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5481
5482        path = path.rstrip('/') if path != '/' else path
5483        if not seafile_api.get_dir_id_by_path(repo_id, path):
5484            error_msg = 'Folder %s not found.' % path
5485            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5486
5487        # permission check
5488        if is_org_context(request):
5489            repo_owner = seafile_api.get_org_repo_owner(repo_id)
5490        else:
5491            repo_owner = seafile_api.get_repo_owner(repo_id)
5492
5493        username = request.user.username
5494        if not (is_pro_version() and can_set_folder_perm_by_user(username, repo, repo_owner)):
5495            error_msg = 'Permission denied.'
5496            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
5497
5498        # add repo user folder perm
5499        result = {}
5500        result['failed'] = []
5501        result['success'] = []
5502
5503        users = request.data.getlist('user_email')
5504        for user in users:
5505            if not is_valid_username(user):
5506                result['failed'].append({
5507                    'user_email': user,
5508                    'error_msg': 'user_email invalid.'
5509                })
5510                continue
5511
5512            try:
5513                User.objects.get(email=user)
5514            except User.DoesNotExist:
5515                result['failed'].append({
5516                    'user_email': user,
5517                    'error_msg': 'User %s not found.' % user
5518                })
5519                continue
5520
5521            permission = seafile_api.get_folder_user_perm(repo_id, path, user)
5522            if permission:
5523                result['failed'].append({
5524                    'user_email': user,
5525                    'error_msg': 'Permission already exists.'
5526                })
5527                continue
5528
5529            try:
5530                seafile_api.add_folder_user_perm(repo_id, path, perm, user)
5531                send_perm_audit_msg('add-repo-perm', username, user, repo_id, path, perm)
5532            except SearpcError as e:
5533                logger.error(e)
5534                result['failed'].append({
5535                    'user_email': user,
5536                    'error_msg': 'Internal Server Error'
5537                })
5538
5539            new_perm = seafile_api.get_folder_user_perm(repo_id, path, user)
5540            new_perm_info = self._get_user_folder_perm_info(
5541                    user, repo_id, path, new_perm)
5542            result['success'].append(new_perm_info)
5543
5544        return Response(result)
5545
5546    def put(self, request, repo_id, format=None):
5547        """ Modify repo user folder perm.
5548
5549        Permission checking:
5550        1. ( repo owner | admin ) & pro edition & enable folder perm.
5551        """
5552
5553        # argument check
5554        path = request.data.get('folder_path', None)
5555        if not path:
5556            error_msg = 'folder_path invalid.'
5557            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5558
5559        perm = request.data.get('permission', None)
5560        if not perm or perm not in get_available_repo_perms():
5561            error_msg = 'permission invalid.'
5562            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5563
5564        user = request.data.get('user_email', None)
5565        if not user:
5566            error_msg = 'user_email invalid.'
5567            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5568
5569        # resource check
5570        repo = seafile_api.get_repo(repo_id)
5571        if not repo:
5572            error_msg = 'Library %s not found.' % repo_id
5573            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5574
5575        path = path.rstrip('/') if path != '/' else path
5576        if not seafile_api.get_dir_id_by_path(repo_id, path):
5577            error_msg = 'Folder %s not found.' % path
5578            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5579
5580        try:
5581            User.objects.get(email=user)
5582        except User.DoesNotExist:
5583            error_msg = 'User %s not found.' % user
5584            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5585
5586        # permission check
5587        if is_org_context(request):
5588            repo_owner = seafile_api.get_org_repo_owner(repo_id)
5589        else:
5590            repo_owner = seafile_api.get_repo_owner(repo_id)
5591
5592        username = request.user.username
5593        if not (is_pro_version() and can_set_folder_perm_by_user(username, repo, repo_owner)):
5594            error_msg = 'Permission denied.'
5595            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
5596
5597        permission = seafile_api.get_folder_user_perm(repo_id, path, user)
5598        if not permission:
5599            error_msg = 'Folder permission not found.'
5600            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5601
5602        # modify permission
5603        try:
5604            seafile_api.set_folder_user_perm(repo_id, path, perm, user)
5605            send_perm_audit_msg('modify-repo-perm', username, user, repo_id, path, perm)
5606            new_perm = seafile_api.get_folder_user_perm(repo_id, path, user)
5607            result = self._get_user_folder_perm_info(user, repo_id, path, new_perm)
5608            return Response(result)
5609        except SearpcError as e:
5610            logger.error(e)
5611            error_msg = 'Internal Server Error'
5612            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
5613
5614    def delete(self, request, repo_id, format=None):
5615        """ Remove repo user folder perms.
5616
5617        Permission checking:
5618        1. ( repo owner | admin ) & pro edition & enable folder perm.
5619        """
5620
5621        # argument check
5622        user = request.data.get('user_email', None)
5623        path = request.data.get('folder_path', None)
5624
5625        if not user:
5626            error_msg = 'user_email invalid.'
5627            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5628
5629        if not path:
5630            error_msg = 'folder_path invalid.'
5631            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5632
5633        # resource check
5634        repo = seafile_api.get_repo(repo_id)
5635        if not repo:
5636            error_msg = 'Library %s not found.' % repo_id
5637            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5638
5639        try:
5640            User.objects.get(email=user)
5641        except User.DoesNotExist:
5642            error_msg = 'User %s not found.' % user
5643            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5644
5645        # permission check
5646        if is_org_context(request):
5647            repo_owner = seafile_api.get_org_repo_owner(repo_id)
5648        else:
5649            repo_owner = seafile_api.get_repo_owner(repo_id)
5650
5651        username = request.user.username
5652        if not (is_pro_version() and can_set_folder_perm_by_user(username, repo, repo_owner)):
5653            error_msg = 'Permission denied.'
5654            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
5655
5656        # delete permission
5657        path = path.rstrip('/') if path != '/' else path
5658        permission = seafile_api.get_folder_user_perm(repo_id, path, user)
5659        if not permission:
5660            return Response({'success': True})
5661
5662        try:
5663            seafile_api.rm_folder_user_perm(repo_id, path, user)
5664            send_perm_audit_msg('delete-repo-perm', username,
5665                    user, repo_id, path, permission)
5666            return Response({'success': True})
5667        except SearpcError as e:
5668            logger.error(e)
5669            error_msg = 'Internal Server Error'
5670            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
5671
5672class RepoGroupFolderPerm(APIView):
5673    authentication_classes = (TokenAuthentication, SessionAuthentication)
5674    permission_classes = (IsAuthenticated,)
5675    throttle_classes = (UserRateThrottle,)
5676
5677    def _get_group_folder_perm_info(self, group_id, repo_id, path, perm):
5678        result = {}
5679        if group_id and repo_id and path and perm:
5680            group = ccnet_api.get_group(group_id)
5681            result['repo_id'] = repo_id
5682            result['group_id'] = group_id
5683            result['group_name'] = group.group_name
5684            result['folder_path'] = path
5685            result['folder_name'] = path if path == '/' else os.path.basename(path.rstrip('/'))
5686            result['permission'] = perm
5687
5688        return result
5689
5690    def get(self, request, repo_id, format=None):
5691        """ List repo group folder perms (by folder_path).
5692
5693        Permission checking:
5694        1. ( repo owner | admin ) & pro edition & enable folder perm.
5695        """
5696
5697        # resource check
5698        repo = seafile_api.get_repo(repo_id)
5699        if not repo:
5700            error_msg = 'Library %s not found.' % repo_id
5701            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5702
5703        # permission check
5704        if is_org_context(request):
5705            repo_owner = seafile_api.get_org_repo_owner(repo_id)
5706        else:
5707            repo_owner = seafile_api.get_repo_owner(repo_id)
5708
5709        username = request.user.username
5710        if not (is_pro_version() and can_set_folder_perm_by_user(username, repo, repo_owner)):
5711            error_msg = 'Permission denied.'
5712            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
5713
5714        results = []
5715        path = request.GET.get('folder_path', None)
5716        group_folder_perms = seafile_api.list_folder_group_perm_by_repo(repo_id)
5717        for perm in group_folder_perms:
5718            result = {}
5719            if path:
5720                if path == perm.path:
5721                    result = self._get_group_folder_perm_info(
5722                            perm.group_id, perm.repo_id, perm.path,
5723                            perm.permission)
5724            else:
5725                result = self._get_group_folder_perm_info(
5726                        perm.group_id, perm.repo_id, perm.path,
5727                        perm.permission)
5728
5729            if result:
5730                results.append(result)
5731        return Response(results)
5732
5733    def post(self, request, repo_id, format=None):
5734        """ Add repo group folder perm.
5735
5736        Permission checking:
5737        1. ( repo owner | admin ) & pro edition & enable folder perm.
5738        """
5739
5740        # argument check
5741        path = request.data.get('folder_path', None)
5742        if not path:
5743            error_msg = 'folder_path invalid.'
5744            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5745
5746        perm = request.data.get('permission', None)
5747        if not perm or perm not in get_available_repo_perms():
5748            error_msg = 'permission invalid.'
5749            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5750
5751        # resource check
5752        repo = seafile_api.get_repo(repo_id)
5753        if not repo:
5754            error_msg = 'Library %s not found.' % repo_id
5755            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5756
5757        path = path.rstrip('/') if path != '/' else path
5758        if not seafile_api.get_dir_id_by_path(repo_id, path):
5759            error_msg = 'Folder %s not found.' % path
5760            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5761
5762        # permission check
5763        if is_org_context(request):
5764            repo_owner = seafile_api.get_org_repo_owner(repo_id)
5765        else:
5766            repo_owner = seafile_api.get_repo_owner(repo_id)
5767
5768        username = request.user.username
5769        if not (is_pro_version() and can_set_folder_perm_by_user(username, repo, repo_owner)):
5770            error_msg = 'Permission denied.'
5771            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
5772
5773        result = {}
5774        result['failed'] = []
5775        result['success'] = []
5776
5777        group_ids = request.data.getlist('group_id')
5778        for group_id in group_ids:
5779            try:
5780                group_id = int(group_id)
5781            except ValueError:
5782                result['failed'].append({
5783                    'group_id': group_id,
5784                    'error_msg': 'group_id invalid.'
5785                })
5786                continue
5787
5788            if not ccnet_api.get_group(group_id):
5789                result['failed'].append({
5790                    'group_id': group_id,
5791                    'error_msg': 'Group %s not found.' % group_id
5792                })
5793                continue
5794
5795            permission = seafile_api.get_folder_group_perm(repo_id, path, group_id)
5796            if permission:
5797                result['failed'].append({
5798                    'group_id': group_id,
5799                    'error_msg': 'Permission already exists.'
5800                })
5801                continue
5802
5803            try:
5804                seafile_api.add_folder_group_perm(repo_id, path, perm, group_id)
5805                send_perm_audit_msg('add-repo-perm', username, group_id, repo_id, path, perm)
5806            except SearpcError as e:
5807                logger.error(e)
5808                result['failed'].append({
5809                    'group_id': group_id,
5810                    'error_msg': 'Internal Server Error'
5811                })
5812
5813            new_perm = seafile_api.get_folder_group_perm(repo_id, path, group_id)
5814            new_perm_info = self._get_group_folder_perm_info(
5815                    group_id, repo_id, path, new_perm)
5816            result['success'].append(new_perm_info)
5817
5818        return Response(result)
5819
5820    def put(self, request, repo_id, format=None):
5821        """ Modify repo group folder perm.
5822
5823        Permission checking:
5824        1. ( repo owner | admin ) & pro edition & enable folder perm.
5825        """
5826
5827        # argument check
5828        path = request.data.get('folder_path', None)
5829        if not path:
5830            error_msg = 'folder_path invalid.'
5831            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5832
5833        perm = request.data.get('permission', None)
5834        if not perm or perm not in get_available_repo_perms():
5835            error_msg = 'permission invalid.'
5836            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5837
5838        group_id = request.data.get('group_id')
5839        if not group_id:
5840            error_msg = 'group_id invalid.'
5841            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5842
5843        try:
5844            group_id = int(group_id)
5845        except ValueError:
5846            error_msg = 'group_id invalid.'
5847            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5848
5849        # resource check
5850        repo = seafile_api.get_repo(repo_id)
5851        if not repo:
5852            error_msg = 'Library %s not found.' % repo_id
5853            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5854
5855        path = path.rstrip('/') if path != '/' else path
5856        if not seafile_api.get_dir_id_by_path(repo_id, path):
5857            error_msg = 'Folder %s not found.' % path
5858            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5859
5860        if not ccnet_api.get_group(group_id):
5861            error_msg = 'Group %s not found.' % group_id
5862            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5863
5864        # permission check
5865        if is_org_context(request):
5866            repo_owner = seafile_api.get_org_repo_owner(repo_id)
5867        else:
5868            repo_owner = seafile_api.get_repo_owner(repo_id)
5869
5870        username = request.user.username
5871        if not (is_pro_version() and can_set_folder_perm_by_user(username, repo, repo_owner)):
5872            error_msg = 'Permission denied.'
5873            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
5874
5875        permission = seafile_api.get_folder_group_perm(repo_id, path, group_id)
5876        if not permission:
5877            error_msg = 'Folder permission not found.'
5878            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5879
5880        # modify permission
5881        try:
5882            seafile_api.set_folder_group_perm(repo_id, path, perm, group_id)
5883            send_perm_audit_msg('modify-repo-perm', username, group_id, repo_id, path, perm)
5884            new_perm = seafile_api.get_folder_group_perm(repo_id, path, group_id)
5885            result = self._get_group_folder_perm_info(group_id, repo_id, path, new_perm)
5886            return Response(result)
5887        except SearpcError as e:
5888            logger.error(e)
5889            error_msg = 'Internal Server Error'
5890            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
5891
5892    def delete(self, request, repo_id, format=None):
5893        """ Remove repo group folder perm.
5894
5895        Permission checking:
5896        1. ( repo owner | admin ) & pro edition & enable folder perm.
5897        """
5898        # arguments check
5899        group_id = request.data.get('group_id', None)
5900        path = request.data.get('folder_path', None)
5901
5902        if not group_id:
5903            error_msg = 'group_id invalid.'
5904            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5905
5906        if not path:
5907            error_msg = 'folder_path invalid.'
5908            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5909
5910        try:
5911            group_id = int(group_id)
5912        except ValueError:
5913            error_msg = 'group_id invalid.'
5914            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5915
5916        # resource check
5917        if not ccnet_api.get_group(group_id):
5918            error_msg = 'Group %s not found.' % group_id
5919            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5920
5921        repo = seafile_api.get_repo(repo_id)
5922        if not repo:
5923            error_msg = 'Library %s not found.' % repo_id
5924            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5925
5926        # permission check
5927        if is_org_context(request):
5928            repo_owner = seafile_api.get_org_repo_owner(repo_id)
5929        else:
5930            repo_owner = seafile_api.get_repo_owner(repo_id)
5931
5932        username = request.user.username
5933        if not (is_pro_version() and can_set_folder_perm_by_user(username, repo, repo_owner)):
5934            error_msg = 'Permission denied.'
5935            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
5936
5937        # delete permission
5938        path = path.rstrip('/') if path != '/' else path
5939        permission = seafile_api.get_folder_group_perm(repo_id, path, group_id)
5940        if not permission:
5941            return Response({'success': True})
5942
5943        try:
5944            seafile_api.rm_folder_group_perm(repo_id, path, group_id)
5945            send_perm_audit_msg('delete-repo-perm', username, group_id,
5946                                repo_id, path, permission)
5947            return Response({'success': True})
5948        except SearpcError as e:
5949            logger.error(e)
5950            error_msg = 'Internal Server Error'
5951            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
5952
5953
5954class RemoteWipeReportView(APIView):
5955    throttle_classes = (UserRateThrottle,)
5956
5957    @json_response
5958    def post(self, request):
5959        token = request.data.get('token', '')
5960        if not token or len(token) != 40:
5961            error_msg = 'token invalid.'
5962            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
5963
5964        try:
5965            entry = TokenV2.objects.get(key=token)
5966        except TokenV2.DoesNotExist:
5967            error_msg = 'token %s not found.' % token
5968            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
5969        else:
5970            if not entry.wiped_at:
5971                return api_error(status.HTTP_400_BAD_REQUEST, "invalid device token")
5972            entry.delete()
5973
5974        return {}
5975