# encoding: utf-8 import os import stat import simplejson as json from urllib2 import unquote, quote import seahub.settings as settings from rest_framework import parsers from rest_framework import status from rest_framework import renderers from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from django.contrib.sites.models import RequestSite from django.http import HttpResponse from models import Token from mime import get_file_mime from authentication import TokenAuthentication from serializers import AuthTokenSerializer from base.accounts import User from share.models import FileShare from seahub.views import access_to_repo, validate_owner from seahub.utils import gen_file_get_url, gen_token, gen_file_upload_url, \ check_filename_with_rename, get_starred_files, get_ccnetapplet_root, \ get_ccnet_server_addr_port from pysearpc import SearpcError from seaserv import seafserv_rpc, seafserv_threaded_rpc, \ get_personal_groups_by_user, get_session_info, get_repo_token_nonnull, \ get_group_repos, get_repo, check_permission, get_commits json_content_type = 'application/json; charset=utf-8' class Ping(APIView): """ Returns a simple `pong` message when client calls `api2/ping/`. For example: curl http://127.0.0.1:8000/api2/ping/ """ def get(self, request, format=None): return Response('pong') def head(self, request, format=None): return Response(headers={'foo': 'bar',}) class AuthPing(APIView): """ Returns a simple `pong` message when client provided an auth token. For example: curl -H "Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" http://127.0.0.1:8000/api2/auth/ping/ """ authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def get(self, request, format=None): return Response('pong') class ObtainAuthToken(APIView): """ Returns auth token if username and password are valid. For example: curl -d "username=xiez1989@gmail.com&password=123456" http://127.0.0.1:8000/api2/auth-token/ """ throttle_classes = () permission_classes = () parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,) renderer_classes = (renderers.JSONRenderer,) model = Token def post(self, request): serializer = AuthTokenSerializer(data=request.DATA) if serializer.is_valid(): token, created = Token.objects.get_or_create(user=serializer.object['user'].username) return Response({'token': token.key}) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) HTTP_ERRORS = { '400':'Bad arguments', '401':'Login required', '402':'Incorrect repo password', '403':'Can not access repo', '404':'Repo not found', '405':'Query password set error', '406':'Repo is not encrypted', '407':'Method not supported', '408':'Login failed', '409':'Repo password required', '410':'Path does not exist', '411':'Failed to get dirid by path', '412':'Failed to get fileid by path', '413':'Above quota', '415':'Operation not supported', '416':'Failed to list dir', '417':'Set password error', '418':'Failed to delete', '419':'Failed to move', '420':'Failed to rename', '421':'Failed to mkdir', '499':'Unknow Error', '500':'Internal server error', '501':'Failed to get shared link', '502':'Failed to send shared link', } def api_error(code='499', msg=None): err_resp = {'error_msg': msg if msg else HTTP_ERRORS[code]} return Response(err_resp, status=code) class Account(APIView): """ Show account info. """ authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def get(self, request, format=None): info = {} email = request.user.username info['email'] = email info['usage'] = seafserv_threaded_rpc.get_user_quota_usage(email) info['total'] = seafserv_threaded_rpc.get_user_quota(email) info['feedback'] = settings.DEFAULT_FROM_EMAIL return Response(info) def calculate_repo_info(repo_list, username): """ Get some info for repo. """ for repo in repo_list: try: commit = get_commits(repo.id, 0, 1)[0] repo.latest_modify = commit.ctime repo.root = commit.root_id repo.size = seafserv_threaded_rpc.server_repo_size(repo.id) if not repo.size : repo.size = 0; password_need = False if repo.encrypted: try: ret = seafserv_rpc.is_passwd_set(repo.id, username) if ret != 1: password_need = True except SearpcErroe, e: pass repo.password_need = password_need except Exception,e: repo.latest_modify = 0 repo.commit = None repo.size = -1 repo.password_need = None class Repos(APIView): authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def get(self, request, format=None): email = request.user.username owned_repos = seafserv_threaded_rpc.list_owned_repos(email) calculate_repo_info (owned_repos, email) owned_repos.sort(lambda x, y: cmp(y.latest_modify, x.latest_modify)) n_repos = seafserv_threaded_rpc.list_share_repos(email, 'to_email', -1, -1) calculate_repo_info (n_repos, email) owned_repos.sort(lambda x, y: cmp(y.latest_modify, x.latest_modify)) repos_json = [] for r in owned_repos: repo = { "type":"repo", "id":r.id, "owner":email, "name":r.name, "desc":r.desc, "mtime":r.latest_modify, "root":r.root, "size":r.size, "encrypted":r.encrypted, "password_need":r.password_need, } repos_json.append(repo) for r in n_repos: repo = { "type":"srepo", "id":r.id, "owner":r.shared_email, "name":r.name, "desc":r.desc, "mtime":r.latest_modify, "root":r.root, "size":r.size, "encrypted":r.encrypted, "password_need":r.password_need, } repos_json.append(repo) groups = get_personal_groups_by_user(email) for group in groups: g_repos = get_group_repos(group.id, email) calculate_repo_info (g_repos, email) owned_repos.sort(lambda x, y: cmp(y.latest_modify, x.latest_modify)) for r in g_repos: repo = { "type":"grepo", "id":r.id, "owner":group.group_name, "name":r.name, "desc":r.desc, "mtime":r.latest_modify, "root":r.root, "size":r.size, "encrypted":r.encrypted, "password_need":r.password_need, } repos_json.append(repo) return Response(repos_json) def can_access_repo(request, repo_id): if not check_permission(repo_id, request.user.username): return False return True class Repo(APIView): authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def head(self, request, repo_id, format=None): # TODO assert False def get(self, request, repo_id, format=None): # check whether user can view repo repo = get_repo(repo_id) if not repo: return api_error('404') if not can_access_repo(request, repo.id): return api_error('403') # check whether use is repo owner if validate_owner(request, repo_id): owner = "self" else: owner = "share" try: repo.latest_modify = get_commits(repo.id, 0, 1)[0].ctime except: repo.latest_modify = None # query repo infomation repo.size = seafserv_threaded_rpc.server_repo_size(repo_id) current_commit = get_commits(repo_id, 0, 1)[0] # generate download url for client ccnet_applet_root = get_ccnetapplet_root() relay_id = get_session_info().id addr, port = get_ccnet_server_addr_port () email = quote(request.user.username) token = get_repo_token_nonnull(repo_id, request.user.username) quote_repo_name = quote(repo.name.encode('utf-8')) enc = 1 if repo.encrypted else '' url = ccnet_applet_root + "/repo/download/" url += "?relay_id=%s&relay_addr=%s&relay_port=%s" % (relay_id, addr, port) url += "&email=%s&token=%s" % (email, token) url += "&repo_id=%s&repo_name=%s&encrypted=%s" % (repo_id, quote_repo_name, enc) repo_json = { "type":"repo", "id":repo.id, "owner":owner, "name":repo.name, "desc":repo.desc, "mtime":repo.lastest_modify, "size":repo.size, "encrypted":repo.encrypted, "root":current_commit.root_id, "password_need":repo.password_need, "download_url": url, } return Response(repo_json) def post(self, request, repo_id, format=None): resp = check_repo_access_permission(request, get_repo(repo_id)) if resp: return resp op = request.GET.get('op', 'setpassword') if op == 'setpassword': return Response("success") return Response("unsupported operation") def check_repo_access_permission(request, repo): if not repo: return api_error('404') if not can_access_repo(request, repo.id): return api_error('403') password_set = False if repo.encrypted: try: ret = seafserv_rpc.is_passwd_set(repo.id, request.user.username) if ret == 1: password_set = True except SearpcError, e: return api_error('405', "SearpcError:" + e.msg) if not password_set: password = request.REQUEST.get('password', default=None) if not password: return api_error('409') return set_repo_password(request, repo, password) def get_file_size (id): size = seafserv_threaded_rpc.get_file_size(id) return size if size else 0 def get_dir_entrys_by_id(request, dir_id): dentrys = [] try: dirs = seafserv_threaded_rpc.list_dir(dir_id) except SearpcError, e: return api_error("416") for dirent in dirs: dtype = "file" entry={} if stat.S_ISDIR(dirent.mode): dtype = "dir" else: mime = get_file_mime(dirent.obj_name) if mime: entry["mime"] = mime try: entry["size"] = get_file_size(dirent.obj_id) except Exception, e: entry["size"]=0 entry["type"]=dtype entry["name"]=dirent.obj_name entry["id"]=dirent.obj_id dentrys.append(entry) #return Response(dentrys) response = HttpResponse(json.dumps(dentrys), status=200, content_type=json_content_type) response["oid"] = dir_id return response class RepoDirents(APIView): """ List directory entries of a repo. TODO: may be better use dirent id instead of path. """ authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def get(self, request, repo_id): repo = get_repo(repo_id) resp = check_repo_access_permission(request, repo) if resp: return resp current_commit = get_commits(repo_id, 0, 1)[0] path = request.GET.get('p', '/') if path[-1] != '/': path = path + '/' dir_id = None try: dir_id = seafserv_threaded_rpc.get_dirid_by_path(current_commit.id, path.encode('utf-8')) except SearpcError, e: return api_error("411", "SearpcError:" + e.msg) if not dir_id: return api_error('410') old_oid = request.GET.get('oid', None) if old_oid and old_oid == dir_id : response = HttpResponse(json.dumps("uptodate"), status=200, content_type=json_content_type) response["oid"] = dir_id return response else: return get_dir_entrys_by_id(request, dir_id) class RepoDirs(APIView): """ List directory entries based on dir_id. """ authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def get(self, request, repo_id, dir_id, format=None): repo = get_repo(repo_id) resp = check_repo_access_permission(request, repo) if resp: return resp return get_dir_entrys_by_id(request, dir_id) def get_shared_link(request, repo_id, path): l = FileShare.objects.filter(repo_id=repo_id).filter(\ username=request.user.username).filter(path=path) token = None if len(l) > 0: fileshare = l[0] token = fileshare.token else: token = gen_token(max_length=10) fs = FileShare() fs.username = request.user.username fs.repo_id = repo_id fs.path = path fs.token = token try: fs.save() except IntegrityError, e: return api_err('501') domain = RequestSite(request).domain file_shared_link = 'http://%s%sf/%s/' % (domain, settings.SITE_ROOT, token) return Response(file_shared_link) def get_repo_file(request, repo_id, file_id, file_name, op): if op == 'download': token = seafserv_rpc.web_get_access_token(repo_id, file_id, op, request.user.username) redirect_url = gen_file_get_url(token, file_name) #return Response(redirect_url) response = HttpResponse(json.dumps(redirect_url), status=200, content_type=json_content_type) response["oid"] = file_id return response if op == 'sharelink': path = request.GET.get('p', None) assert path, 'path must be passed in the url' return get_shared_link(request, repo_id, path) class RepoFilepath(APIView): """ """ authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def get(self, request, repo_id, format=None): repo = get_repo(repo_id) resp = check_repo_access_permission(request, repo) if resp: return resp path = request.GET.get('p', None) if not path: return api_error('413') file_id = None try: file_id = seafserv_threaded_rpc.get_file_by_path(repo_id, path.encode('utf-8')) except SearpcError, e: return api_error('412', "SearpcError:" + e.msg) if not file_id: return api_error('410') file_name = request.GET.get('file_name', file_id) op = request.GET.get('op', 'download') return get_repo_file(request, repo_id, file_id, file_name, op) def post(self, request, repo_id, format=None): repo = get_repo(repo_id) resp = check_repo_access_permission(request, repo) if resp: return resp path = request.GET.get('p', None) if not path: return api_error('413', 'Path needed') op = request.GET.get('op', 'sendsharelink') if op == 'sendsharelink': emails = request.POST.get('email', None) if not emails: return api_error('400', "Email required") return send_share_link(request, path, emails) elif op == 'star': org_id = int(request.GET.get('org', '-1')) star_file(request.user.username, repo_id, path, False, org_id=org_id) return HttpResponse('success') elif op == 'unstar': unstar_file(request.user.username, repo_id, path) return HttpResponse('success') return api_error('415') class RepoFiles(APIView): """ """ authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def get(self, request, repo_id, file_id, format=None): repo = get_repo(repo_id) resp = check_repo_access_permission(request, repo) if resp: return resp file_name = request.GET.get('file_name', file_id) return get_repo_file(request, repo_id, file_id, file_name, 'download') def reloaddir_if_neccessary (request, repo_id, parent_dir): reloaddir = False s = request.GET.get('reloaddir', None) if s and s.lower() == 'true': reloaddir = True if not reloaddir: return Response('success') current_commit = get_commits(repo_id, 0, 1)[0] try: dir_id = seafserv_threaded_rpc.get_dirid_by_path(current_commit.id, parent_dir.encode('utf-8')) except SearpcError, e: return api_error("411", "SearpcError:" + e.msg) if not dir_id: return api_error('410') return get_dir_entrys_by_id(request, dir_id) class OpDeleteView(APIView): """ Delete a file. """ authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def post(self, request, repo_id, format=None): resp = check_repo_access_permission(request, get_repo(repo_id)) if resp: return resp parent_dir = request.GET.get('p', '/') file_names = request.POST.get("file_names") if not parent_dir or not file_names: return api_error('400') names = file_names.split(':') names = map(lambda x: unquote(x).decode('utf-8'), names) for file_name in names: try: seafserv_threaded_rpc.del_file(repo_id, parent_dir, file_name, request.user.username) except SearpcError,e: return api_error('418', 'SearpcError:' + e.msg) return reloaddir_if_neccessary (request, repo_id, parent_dir) class OpRenameView(APIView): """ Rename a file. """ authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def post(self, request, repo_id, format=None): resp = check_repo_access_permission(request, get_repo(repo_id)) if resp: return resp path = request.GET.get('p') newname = request.POST.get("newname") if not path or path[0] != '/' or not newname: return api_error('400') newname = unquote(newname).decode('utf-8') if len(newname) > settings.MAX_UPLOAD_FILE_NAME_LEN: return api_error('420', 'New name too long') parent_dir = os.path.dirname(path) oldname = os.path.basename(path) if oldname == newname: return api_error('420', 'The new name is the same to the old') newname = check_filename_with_rename(repo_id, parent_dir, newname) try: seafserv_threaded_rpc.rename_file (repo_id, parent_dir, oldname, newname, request.user.username) except SearpcError,e: return api_error('420', "SearpcError:" + e.msg) return reloaddir_if_neccessary (request, repo_id, parent_dir) class OpMoveView(APIView): """ Move a file. TODO: should be refactored and splited. """ authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def post(self, request, repo_id, format=None): src_repo_id = request.POST.get('src_repo') src_dir = unquote(request.POST.get('src_dir')).decode('utf-8') dst_repo_id = request.POST.get('dst_repo') dst_dir = unquote(request.POST.get('dst_dir')).decode('utf-8') op = request.POST.get('operation') obj_names = request.POST.get('obj_names') if not (src_repo_id and src_dir and dst_repo_id \ and dst_dir and op and obj_names): return api_error('400') if src_repo_id == dst_repo_id and src_dir == dst_dir: return api_error('419', 'The src_dir is same to dst_dir') names = obj_names.split(':') names = map(lambda x: unquote(x).decode('utf-8'), names) if dst_dir.startswith(src_dir): for obj_name in names: if dst_dir.startswith('/'.join([src_dir, obj_name])): return api_error('419', 'Can not move a dirctory to its subdir') for obj_name in names: new_obj_name = check_filename_with_rename(dst_repo_id, dst_dir, obj_name) try: if op == 'cp': seafserv_threaded_rpc.copy_file (src_repo_id, src_dir, obj_name, dst_repo_id, dst_dir, new_obj_name, request.user.username) elif op == 'mv': seafserv_threaded_rpc.move_file (src_repo_id, src_dir, obj_name, dst_repo_id, dst_dir, new_obj_name, request.user.username) except SearpcError, e: return api_error('419', "SearpcError:" + e.msg) return reloaddir_if_neccessary (request, dst_repo_id, dst_dir) class OpMkdirView(APIView): """ Make a new directory. """ authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def post(self, request, repo_id, format=None): resp = check_repo_access_permission(request, get_repo(repo_id)) if resp: return resp path = request.GET.get('p') if not path or path[0] != '/': return api_error('400') parent_dir = os.path.dirname(path) new_dir_name = os.path.basename(path) new_dir_name = check_filename_with_rename(repo_id, parent_dir, new_dir_name) try: seafserv_threaded_rpc.post_dir(repo_id, parent_dir, new_dir_name, request.user.username) except SearpcError, e: return api_error('421', e.msg) return reloaddir_if_neccessary (request, repo_id, parent_dir) class OpUploadView(APIView): """ Upload a file. """ authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def get(self, request, repo_id, format=None): repo = get_repo(repo_id) if check_permission(repo_id, request.user.username) == 'rw': token = seafserv_rpc.web_get_access_token(repo_id, 'dummy', 'upload', request.user.username) else: return api_error('403') if request.cloud_mode and seafserv_threaded_rpc.check_quota(repo_id) < 0: return api_error('413') upload_url = gen_file_upload_url(token, 'upload') return Response(upload_url) def append_starred_files(array, files): for f in files: sfile = {'org' : f.org_id, 'repo' : f.repo.id, 'path' : f.path, 'mtime' : f.last_modified, 'dir' : f.is_dir, 'size' : f.size } array.append(sfile) def api_starred_files(request): starred_files = [] personal_files = get_starred_files(request.user.username, -1) append_starred_files (starred_files, personal_files) return Response(starred_files) class StarredFileView(APIView): """ Get starred files list. """ authentication_classes = (TokenAuthentication, ) permission_classes = (IsAuthenticated,) def get(self, request, format=None): return api_starred_files(request)