1# Copyright (c) 2012-2016 Seafile Ltd. 2# encoding: utf-8 3# Utility functions for api2 4 5import os 6import time 7import json 8import re 9import logging 10 11from collections import defaultdict 12from functools import wraps 13 14from django.http import HttpResponse 15from rest_framework.authentication import SessionAuthentication 16from rest_framework.response import Response 17from rest_framework import status, serializers 18from seaserv import seafile_api, ccnet_api, \ 19 get_group, seafserv_threaded_rpc 20from pysearpc import SearpcError 21 22from seahub.base.templatetags.seahub_tags import email2nickname, \ 23 translate_seahub_time, file_icon_filter, email2contact_email 24from seahub.group.views import is_group_staff 25from seahub.group.utils import is_group_member 26from seahub.notifications.models import UserNotification 27from seahub.api2.models import Token, TokenV2, DESKTOP_PLATFORMS 28from seahub.avatar.settings import AVATAR_DEFAULT_SIZE 29from seahub.avatar.templatetags.avatar_tags import api_avatar_url 30from seahub.settings import INSTALLED_APPS 31 32logger = logging.getLogger(__name__) 33 34def api_error(code, msg): 35 err_resp = {'error_msg': msg} 36 return Response(err_resp, status=code) 37 38def get_file_size(store_id, repo_version, file_id): 39 size = seafile_api.get_file_size(store_id, repo_version, file_id) 40 return size if size else 0 41 42def prepare_starred_files(files): 43 array = [] 44 for f in files: 45 sfile = {'org' : f.org_id, 46 'repo' : f.repo.id, 47 'repo_id' : f.repo.id, 48 'repo_name' : f.repo.name, 49 'path' : f.path, 50 'icon_path' : file_icon_filter(f.path), 51 'file_name' : os.path.basename(f.path), 52 'mtime' : f.last_modified, 53 'mtime_relative': translate_seahub_time(f.last_modified), 54 'dir' : f.is_dir, 55 'repo_encrypted' : f.repo.encrypted 56 } 57 if not f.is_dir: 58 try: 59 file_id = seafile_api.get_file_id_by_path(f.repo.id, f.path) 60 sfile['oid'] = file_id 61 sfile['size'] = get_file_size(f.repo.store_id, f.repo.version, file_id) 62 except SearpcError as e: 63 logger.error(e) 64 pass 65 66 array.append(sfile) 67 68 return array 69 70def get_groups(email): 71 group_json = [] 72 73 joined_groups = ccnet_api.get_groups(email) 74 grpmsgs = {} 75 for g in joined_groups: 76 grpmsgs[g.id] = 0 77 78 replynum = 0 79 80 for g in joined_groups: 81 group = { 82 "id": g.id, 83 "name": g.group_name, 84 "creator": g.creator_name, 85 "ctime": g.timestamp, 86 "msgnum": grpmsgs[g.id], 87 } 88 group_json.append(group) 89 90 return group_json, replynum 91 92def get_timestamp(msgtimestamp): 93 if not msgtimestamp: 94 return 0 95 timestamp = int(time.mktime(msgtimestamp.timetuple())) 96 return timestamp 97 98def api_group_check(func): 99 """ 100 Decorator for initial group permission check tasks 101 102 un-login user & group not pub --> login page 103 un-login user & group pub --> view_perm = "pub" 104 login user & non group member & group not pub --> public info page 105 login user & non group member & group pub --> view_perm = "pub" 106 group member --> view_perm = "joined" 107 sys admin --> view_perm = "sys_admin" 108 """ 109 def _decorated(view, request, group_id, *args, **kwargs): 110 group_id_int = int(group_id) # Checked by URL Conf 111 group = get_group(group_id_int) 112 if not group: 113 return api_error(status.HTTP_404_NOT_FOUND, 'Group not found.') 114 group.is_staff = False 115 group.is_pub = False 116 117 joined = is_group_member(group_id_int, request.user.username) 118 if joined: 119 group.view_perm = "joined" 120 group.is_staff = is_group_staff(group, request.user) 121 return func(view, request, group, *args, **kwargs) 122 if request.user.is_staff: 123 # viewed by system admin 124 group.view_perm = "sys_admin" 125 return func(view, request, group, *args, **kwargs) 126 127 if group.is_pub: 128 group.view_perm = "pub" 129 return func(view, request, group, *args, **kwargs) 130 131 # Return group public info page. 132 return api_error(status.HTTP_403_FORBIDDEN, 'Forbid to access this group.') 133 134 return _decorated 135 136def get_client_ip(request): 137 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '') 138 if x_forwarded_for: 139 ip = x_forwarded_for.split(',')[0] 140 else: 141 ip = request.META.get('REMOTE_ADDR', '') 142 143 return ip 144 145def get_diff_details(repo_id, commit1, commit2): 146 result = defaultdict(list) 147 148 diff_result = seafserv_threaded_rpc.get_diff(repo_id, commit1, commit2) 149 if not diff_result: 150 return result 151 152 for d in diff_result: 153 if d.status == 'add': 154 result['added_files'].append(d.name) 155 elif d.status == 'del': 156 result['deleted_files'].append(d.name) 157 elif d.status == 'mov': 158 result['renamed_files'].extend((d.name, d.new_name)) 159 elif d.status == 'mod': 160 result['modified_files'].append(d.name) 161 elif d.status == 'newdir': 162 result['added_dirs'].append(d.name) 163 elif d.status == 'deldir': 164 result['deleted_dirs'].append(d.name) 165 166 return result 167 168JSON_CONTENT_TYPE = 'application/json; charset=utf-8' 169def json_response(func): 170 @wraps(func) 171 def wrapped(*a, **kw): 172 result = func(*a, **kw) 173 if isinstance(result, HttpResponse): 174 return result 175 else: 176 return HttpResponse(json.dumps(result), status=200, 177 content_type=JSON_CONTENT_TYPE) 178 return wrapped 179 180def get_token_v1(username): 181 token, _ = Token.objects.get_or_create(user=username) 182 return token 183 184_ANDROID_DEVICE_ID_PATTERN = re.compile('^[a-f0-9]{1,16}$') 185def get_token_v2(request, username, platform, device_id, device_name, 186 client_version, platform_version): 187 188 if platform in DESKTOP_PLATFORMS: 189 # desktop device id is the peer id, so it must be 40 chars 190 if len(device_id) != 40: 191 raise serializers.ValidationError('invalid device id') 192 193 elif platform == 'android': 194 # See http://developer.android.com/reference/android/provider/Settings.Secure.html#ANDROID_ID 195 # android device id is the 64bit secure id, so it must be 16 chars in hex representation 196 # but some user reports their device ids are 14 or 15 chars long. So we relax the validation. 197 if not _ANDROID_DEVICE_ID_PATTERN.match(device_id.lower()): 198 raise serializers.ValidationError('invalid device id') 199 elif platform == 'ios': 200 if len(device_id) != 36: 201 raise serializers.ValidationError('invalid device id') 202 else: 203 raise serializers.ValidationError('invalid platform') 204 205 return TokenV2.objects.get_or_create_token( 206 username, platform, device_id, device_name, 207 client_version, platform_version, get_client_ip(request)) 208 209def get_api_token(request, keys=None, key_prefix='shib_'): 210 211 if not keys: 212 keys = [ 213 'platform', 214 'device_id', 215 'device_name', 216 'client_version', 217 'platform_version', 218 ] 219 220 if key_prefix: 221 keys = [key_prefix + item for item in keys] 222 223 if all([key in request.GET for key in keys]): 224 225 platform = request.GET['%splatform' % key_prefix] 226 device_id = request.GET['%sdevice_id' % key_prefix] 227 device_name = request.GET['%sdevice_name' % key_prefix] 228 client_version = request.GET['%sclient_version' % key_prefix] 229 platform_version = request.GET['%splatform_version' % key_prefix] 230 231 token = get_token_v2(request, request.user.username, platform, 232 device_id, device_name, client_version, 233 platform_version) 234 else: 235 token = get_token_v1(request.user.username) 236 237 return token 238 239def to_python_boolean(string): 240 """Convert a string to boolean. 241 """ 242 string = string.lower() 243 if string in ('t', 'true', '1'): 244 return True 245 if string in ('f', 'false', '0'): 246 return False 247 raise ValueError("Invalid boolean value: '%s'" % string) 248 249def get_user_common_info(email, avatar_size=AVATAR_DEFAULT_SIZE): 250 avatar_url, is_default, date_uploaded = api_avatar_url(email, avatar_size) 251 return { 252 "email": email, 253 "name": email2nickname(email), 254 "contact_email": email2contact_email(email), 255 "avatar_url": avatar_url 256 } 257 258def user_to_dict(email, request=None, avatar_size=AVATAR_DEFAULT_SIZE): 259 d = get_user_common_info(email, avatar_size) 260 return { 261 'user_name': d['name'], 262 'user_email': d['email'], 263 'user_contact_email': d['contact_email'], 264 'avatar_url': d['avatar_url'], 265 } 266 267def is_web_request(request): 268 if isinstance(request.successful_authenticator, SessionAuthentication): 269 return True 270 else: 271 return False 272