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