1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-08 18:30:53 +00:00

Added api v2

This commit is contained in:
zhengxie
2012-12-14 20:53:36 +08:00
parent 4a6232bb12
commit d202012832
12 changed files with 1310 additions and 2 deletions

View File

@@ -375,7 +375,7 @@ class RepoView(ResponseMixin, View):
"desc":repo.desc, "desc":repo.desc,
"mtime":repo.lastest_modify, "mtime":repo.lastest_modify,
"size":repo.size, "size":repo.size,
"encrypted":r.encrypted, "encrypted":r.encrypted, # Typo: repo.encrypted ?
"root":current_commit.root_id, "root":current_commit.root_id,
"password_need":repo.password_need, "password_need":repo.password_need,
} }

0
api2/__init__.py Normal file
View File

11
api2/api.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/sh
curl -d "username=xiez1989@gmail.com&password=123456" http://127.0.0.1:8000/api2/auth-token/
echo ""
curl -H 'Authorization: Token 24fd3c026886e3121b2ca630805ed425c272cb96' -H 'Accept: application/json; indent=4' http://127.0.0.1:8000/api2/repos/e817caa9-7d61-4921-ac2a-0c387cf8576a/
curl -H "Authorization: Token f2210dacd9c6ccb8133606d94ff8e61d99b477fd" -H 'Accept: application/json; indent=4' http://127.0.0.1:8000/api2/repos/99b758e6-91ab-4265-b705-925367374cf0/dirents/
curl -H "Authorization: Token f2210dacd9c6ccb8133606d94ff8e61d99b477fd" -H 'Accept: application/json; indent=4' http://127.0.0.1:8000/api2/repos/99b758e6-91ab-4265-b705-925367374cf0/dirs/7ffb55a74b6e5490111c24b077bd2cf73fb9796e/

36
api2/authentication.py Normal file
View File

@@ -0,0 +1,36 @@
from rest_framework.authentication import BaseAuthentication
from models import Token
from base.accounts import User
class TokenAuthentication(BaseAuthentication):
"""
Simple token based authentication.
Clients should authenticate by passing the token key in the "Authorization"
HTTP header, prepended with the string "Token ". For example:
Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
"""
model = Token
"""
A custom token model may be used, but must have the following properties.
* key -- The string identifying the token
* user -- The user to which the token belongs
"""
def authenticate(self, request):
auth = request.META.get('HTTP_AUTHORIZATION', '').split()
if len(auth) == 2 and auth[0].lower() == "token":
key = auth[1]
try:
token = self.model.objects.get(key=key)
except self.model.DoesNotExist:
return None
user = User.objects.get(email=token.user)
if user.is_active:
return (user, token)

448
api2/mime.py Normal file
View File

@@ -0,0 +1,448 @@
import os
MIME_MAP = {
'3gp': 'video/3gpp',
'aab': 'application/x-authoware-bin',
'aam': 'application/x-authoware-map',
'aas': 'application/x-authoware-seg',
'ai': 'application/postscript',
'aif': 'audio/x-aiff',
'aifc': 'audio/x-aiff',
'aiff': 'audio/x-aiff',
'als': 'audio/X-Alpha5',
'amc': 'application/x-mpeg',
'ani': 'application/octet-stream',
'asc': 'text/plain',
'asd': 'application/astound',
'asf': 'video/x-ms-asf',
'asn': 'application/astound',
'asp': 'application/x-asap',
'asx': 'video/x-ms-asf',
'au': 'audio/basic',
'avb': 'application/octet-stream',
'avi': 'video/x-msvideo',
'awb': 'audio/amr-wb',
'bcpio': 'application/x-bcpio',
'bin': 'application/octet-stream',
'bld': 'application/bld',
'bld2': 'application/bld2',
'bmp': 'application/x-MS-bmp',
'bpk': 'application/octet-stream',
'bz2': 'application/x-bzip2',
'c': 'text/x-c',
'cal': 'image/x-cals',
'cc': 'text/x-c++',
'ccn': 'application/x-cnc',
'cco': 'application/x-cocoa',
'cdf': 'application/x-netcdf',
'cgi': 'magnus-internal/cgi',
'chat': 'application/x-chat',
'class': 'application/octet-stream',
'clp': 'application/x-msclip',
'cmx': 'application/x-cmx',
'co': 'application/x-cult3d-object',
'cod': 'image/cis-cod',
'cpio': 'application/x-cpio',
'cpp': 'text/x-c++',
'cpt': 'application/mac-compactpro',
'crd': 'application/x-mscardfile',
'cs': 'text/x-csharp',
'csh': 'text/x-csh',
'csm': 'chemical/x-csml',
'csml': 'chemical/x-csml',
'css': 'text/css',
'cur': 'application/octet-stream',
'dcm': 'x-lml/x-evm',
'dcr': 'application/x-director',
'dcx': 'image/x-dcx',
'dhtml': 'text/html',
'dir': 'application/x-director',
'dll': 'application/octet-stream',
'dmg': 'application/octet-stream',
'dms': 'application/octet-stream',
'doc': 'application/msword',
'docx': 'application/msword',
'dot': 'application/x-dot',
'dvi': 'application/x-dvi',
'dwf': 'drawing/x-dwf',
'dwg': 'application/x-autocad',
'dxf': 'application/x-autocad',
'dxr': 'application/x-director',
'ebk': 'application/x-expandedbook',
'emb': 'chemical/x-embl-dl-nucleotide',
'embl': 'chemical/x-embl-dl-nucleotide',
'eps': 'application/postscript',
'epub': 'application/epub+zip',
'eri': 'image/x-eri',
'es': 'audio/echospeech',
'esl': 'audio/echospeech',
'etc': 'application/x-earthtime',
'etx': 'text/x-setext',
'evm': 'x-lml/x-evm',
'evy': 'application/x-envoy',
'exe': 'application/octet-stream',
'fh4': 'image/x-freehand',
'fh5': 'image/x-freehand',
'fhc': 'image/x-freehand',
'fif': 'image/fif',
'fm': 'application/x-maker',
'fpx': 'image/x-fpx',
'fvi': 'video/isivideo',
'gau': 'chemical/x-gaussian-input',
'gca': 'application/x-gca-compressed',
'gdb': 'x-lml/x-gdb',
'gif': 'image/gif',
'gps': 'application/x-gps',
'gtar': 'application/x-gtar',
'gz': 'application/x-gzip',
'h': 'text/x-c++hdr',
'hdf': 'application/x-hdf',
'hdm': 'text/x-hdml',
'hdml': 'text/x-hdml',
'hlp': 'application/winhlp',
'hqx': 'application/mac-binhex40',
'htm': 'text/html',
'html': 'text/html',
'hts': 'text/html',
'ice': 'x-conference/x-cooltalk',
'ico': 'application/octet-stream',
'ief': 'image/ief',
'ifm': 'image/gif',
'ifs': 'image/ifs',
'imy': 'audio/melody',
'ins': 'application/x-NET-Install',
'ips': 'application/x-ipscript',
'ipx': 'application/x-ipix',
'it': 'audio/x-mod',
'itz': 'audio/x-mod',
'ivr': 'i-world/i-vrml',
'j2k': 'image/j2k',
'jad': 'text/vnd.sun.j2me.app-descriptor',
'jam': 'application/x-jam',
'jar': 'application/java-archive',
'jnlp': 'application/x-java-jnlp-file',
'jpe': 'image/jpeg',
'jpeg': 'image/jpeg',
'jpg': 'image/jpeg',
'jpz': 'image/jpeg',
'js': 'application/x-javascript',
'jwc': 'application/jwc',
'kjx': 'application/x-kjx',
'lak': 'x-lml/x-lak',
'latex': 'application/x-latex',
'lcc': 'application/fastman',
'lcl': 'application/x-digitalloca',
'lcr': 'application/x-digitalloca',
'lgh': 'application/lgh',
'lha': 'application/octet-stream',
'lml': 'x-lml/x-lml',
'lmlpack': 'x-lml/x-lmlpack',
'lsf': 'video/x-ms-asf',
'lsx': 'video/x-ms-asf',
'lzh': 'application/x-lzh',
'm13': 'application/x-msmediaview',
'm14': 'application/x-msmediaview',
'm15': 'audio/x-mod',
'm3u': 'audio/x-mpegurl',
'm3url': 'audio/x-mpegurl',
'm4': 'application/x-m4',
'ma1': 'audio/ma1',
'ma2': 'audio/ma2',
'ma3': 'audio/ma3',
'ma5': 'audio/ma5',
'man': 'application/x-troff-man',
'map': 'magnus-internal/imagemap',
'markdown': 'text/x-markdown',
'mbd': 'application/mbedlet',
'mct': 'application/x-mascot',
'md': 'text/x-markdown',
'mdb': 'application/x-msaccess',
'mdz': 'audio/x-mod',
'me': 'application/x-troff-me',
'mel': 'text/x-vmel',
'mi': 'application/x-mif',
'mid': 'audio/midi',
'midi': 'audio/midi',
'mif': 'application/x-mif',
'mil': 'image/x-cals',
'mio': 'audio/x-mio',
'mmf': 'application/x-skt-lbs',
'mng': 'video/x-mng',
'mny': 'application/x-msmoney',
'moc': 'application/x-mocha',
'mocha': 'application/x-mocha',
'mod': 'audio/x-mod',
'mof': 'application/x-yumekara',
'mol': 'chemical/x-mdl-molfile',
'mop': 'chemical/x-mopac-input',
'mov': 'video/quicktime',
'movie': 'video/x-sgi-movie',
'mp2': 'audio/x-mpeg',
'mp3': 'audio/x-mpeg',
'mp4': 'video/mp4',
'mpc': 'application/vnd.mpohun.certificate',
'mpe': 'video/mpeg',
'mpeg': 'video/mpeg',
'mpg': 'video/mpeg',
'mpg4': 'video/mp4',
'mpga': 'audio/mpeg',
'mpn': 'application/vnd.mophun.application',
'mpp': 'application/vnd.ms-project',
'mps': 'application/x-mapserver',
'mrl': 'text/x-mrml',
'mrm': 'application/x-mrm',
'ms': 'application/x-troff-ms',
'mts': 'application/metastream',
'mtx': 'application/metastream',
'mtz': 'application/metastream',
'mzv': 'application/metastream',
'nar': 'application/zip',
'nbmp': 'image/nbmp',
'nc': 'application/x-netcdf',
'ndb': 'x-lml/x-ndb',
'ndwn': 'application/ndwn',
'nif': 'application/x-nif',
'nmz': 'application/x-scream',
'nokia-op-logo': 'image/vnd.nok-oplogo-color',
'npx': 'application/x-netfpx',
'nsnd': 'audio/nsnd',
'nva': 'application/x-neva1',
'oda': 'application/oda',
'odb': 'application/vnd.oasis.opendocument.database',
'odf': 'application/vnd.oasis.opendocument.formula',
'odg': 'application/vnd.oasis.opendocument.graphics',
'odi': 'application/vnd.oasis.opendocument.image',
'odp': 'application/vnd.oasis.opendocument.presentation',
'ods': 'application/vnd.oasis.opendocument.spreadsheet',
'odt': 'application/vnd.oasis.opendocument.text',
'oom': 'application/x-AtlasMate-Plugin',
'oth': 'application/vnd.oasis.opendocument.text-web',
'pac': 'audio/x-pac',
'pae': 'audio/x-epac',
'pan': 'application/x-pan',
'pbm': 'image/x-portable-bitmap',
'pcx': 'image/x-pcx',
'pda': 'image/x-pda',
'pdb': 'chemical/x-pdb',
'pdf': 'application/pdf',
'pfr': 'application/font-tdpfr',
'pgm': 'image/x-portable-graymap',
'php': 'application/x-php',
'pict': 'image/x-pict',
'pm': 'application/x-perl',
'pmd': 'application/x-pmd',
'png': 'image/png',
'pnm': 'image/x-portable-anymap',
'pnz': 'image/png',
'pot': 'application/vnd.ms-powerpoint',
'ppm': 'image/x-portable-pixmap',
'pps': 'application/vnd.ms-powerpoint',
'ppt': 'application/vnd.ms-powerpoint',
'pptx': 'application/vnd.ms-powerpoint',
'pqf': 'application/x-cprplayer',
'pqi': 'application/cprplayer',
'prc': 'application/x-prc',
'proxy': 'application/x-ns-proxy-autoconfig',
'ps': 'application/postscript',
'ptlk': 'application/listenup',
'pub': 'application/x-mspublisher',
'pvx': 'video/x-pv-pvx',
'py': 'text/x-python',
'pyc': 'application/x-python-bytecode',
'qcp': 'audio/vnd.qcelp',
'qt': 'video/quicktime',
'qti': 'image/x-quicktime',
'qtif': 'image/x-quicktime',
'r3t': 'text/vnd.rn-realtext3d',
'ra': 'audio/x-pn-realaudio',
'ram': 'audio/x-pn-realaudio',
'rar': 'application/x-rar',
'ras': 'image/x-cmu-raster',
'rdf': 'application/rdf+xml',
'rf': 'image/vnd.rn-realflash',
'rgb': 'image/x-rgb',
'rlf': 'application/x-richlink',
'rm': 'audio/x-pn-realaudio',
'rmf': 'audio/x-rmf',
'rmm': 'audio/x-pn-realaudio',
'rmvb': 'audio/x-pn-realaudio',
'rnx': 'application/vnd.rn-realplayer',
'roff': 'application/x-troff',
'rp': 'image/vnd.rn-realpix',
'rpm': 'audio/x-pn-realaudio-plugin',
'rt': 'text/vnd.rn-realtext',
'rte': 'x-lml/x-gps',
'rtf': 'application/rtf',
'rtg': 'application/metastream',
'rtx': 'text/richtext',
'rv': 'video/vnd.rn-realvideo',
'rwc': 'application/x-rogerwilco',
's3m': 'audio/x-mod',
's3z': 'audio/x-mod',
'sca': 'application/x-supercard',
'scd': 'application/x-msschedule',
'sdf': 'application/e-score',
'sdp': 'application/sdp',
'sea': 'application/x-stuffit',
'sgm': 'text/x-sgml',
'sgml': 'text/x-sgml',
'sh': 'text/x-sh',
'shar': 'application/x-shar',
'shtml': 'magnus-internal/parsed-html',
'shw': 'application/presentations',
'si6': 'image/si6',
'si7': 'image/vnd.stiwap.sis',
'si9': 'image/vnd.lgtwap.sis',
'sis': 'application/vnd.symbian.install',
'sit': 'application/x-stuffit',
'skd': 'application/x-Koan',
'skm': 'application/x-Koan',
'skp': 'application/x-Koan',
'skt': 'application/x-Koan',
'slc': 'application/x-salsa',
'smd': 'audio/x-smd',
'smi': 'application/smil',
'smil': 'application/smil',
'smp': 'application/studiom',
'smz': 'audio/x-smd',
'snd': 'audio/basic',
'spc': 'text/x-speech',
'spl': 'application/futuresplash',
'spr': 'application/x-sprite',
'sprite': 'application/x-sprite',
'spt': 'application/x-spt',
'src': 'application/x-wais-source',
'stk': 'application/hyperstudio',
'stm': 'audio/x-mod',
'sv4cpio': 'application/x-sv4cpio',
'sv4crc': 'application/x-sv4crc',
'svf': 'image/vnd',
'svg': 'image/svg+xml',
'svh': 'image/svh',
'svr': 'x-world/x-svr',
'swf': 'application/x-shockwave-flash',
'swfl': 'application/x-shockwave-flash',
't': 'application/x-troff',
'tad': 'application/octet-stream',
'talk': 'text/x-speech',
'tar': 'application/x-tar',
'taz': 'application/x-tar',
'tbp': 'application/x-timbuktu',
'tbt': 'application/x-timbuktu',
'tcl': 'application/x-tcl',
'tex': 'application/x-tex',
'texi': 'application/x-texinfo',
'texinfo': 'application/x-texinfo',
'tgz': 'application/x-tar',
'thm': 'application/vnd.eri.thm',
'tif': 'image/tiff',
'tiff': 'image/tiff',
'tki': 'application/x-tkined',
'tkined': 'application/x-tkined',
'toc': 'application/toc',
'toy': 'image/toy',
'tr': 'application/x-troff',
'trk': 'x-lml/x-gps',
'trm': 'application/x-msterminal',
'tsi': 'audio/tsplayer',
'tsp': 'application/dsptype',
'tsv': 'text/tab-separated-values',
'ttf': 'application/octet-stream',
'ttz': 'application/t-time',
'txt': 'text/plain',
'ult': 'audio/x-mod',
'ustar': 'application/x-ustar',
'uu': 'application/x-uuencode',
'uue': 'application/x-uuencode',
'vcd': 'application/x-cdlink',
'vcf': 'text/x-vcard',
'vdo': 'video/vdo',
'vib': 'audio/vib',
'viv': 'video/vivo',
'vivo': 'video/vivo',
'vmd': 'application/vocaltec-media-desc',
'vmf': 'application/vocaltec-media-file',
'vmi': 'application/x-dreamcast-vms-info',
'vms': 'application/x-dreamcast-vms',
'vox': 'audio/voxware',
'vqe': 'audio/x-twinvq-plugin',
'vqf': 'audio/x-twinvq',
'vql': 'audio/x-twinvq',
'vre': 'x-world/x-vream',
'vrml': 'x-world/x-vrml',
'vrt': 'x-world/x-vrt',
'vrw': 'x-world/x-vream',
'vts': 'workbook/formulaone',
'wav': 'audio/x-wav',
'wax': 'audio/x-ms-wax',
'wbmp': 'image/vnd.wap.wbmp',
'web': 'application/vnd.xara',
'wi': 'image/wavelet',
'wis': 'application/x-InstallShield',
'wm': 'video/x-ms-wm',
'wma': 'audio/x-ms-wma',
'wmd': 'application/x-ms-wmd',
'wmf': 'application/x-msmetafile',
'wml': 'text/vnd.wap.wml',
'wmlc': 'application/vnd.wap.wmlc',
'wmls': 'text/vnd.wap.wmlscript',
'wmlsc': 'application/vnd.wap.wmlscriptc',
'wmlscript': 'text/vnd.wap.wmlscript',
'wmv': 'video/x-ms-wmv',
'wmx': 'video/x-ms-wmx',
'wmz': 'application/x-ms-wmz',
'wpng': 'image/x-up-wpng',
'wpt': 'x-lml/x-gps',
'wri': 'application/x-mswrite',
'wrl': 'x-world/x-vrml',
'wrz': 'x-world/x-vrml',
'ws': 'text/vnd.wap.wmlscript',
'wsc': 'application/vnd.wap.wmlscriptc',
'wv': 'video/wavelet',
'wvx': 'video/x-ms-wvx',
'wxl': 'application/x-wxl',
'x-gzip': 'application/x-gzip',
'xar': 'application/vnd.xara',
'xbm': 'image/x-xbitmap',
'xdm': 'application/x-xdma',
'xdma': 'application/x-xdma',
'xdw': 'application/vnd.fujixerox.docuworks',
'xht': 'application/xhtml+xml',
'xhtm': 'application/xhtml+xml',
'xhtml': 'application/xhtml+xml',
'xla': 'application/vnd.ms-excel',
'xlc': 'application/vnd.ms-excel',
'xll': 'application/x-excel',
'xlm': 'application/vnd.ms-excel',
'xls': 'application/vnd.ms-excel',
'xlt': 'application/vnd.ms-excel',
'xlw': 'application/vnd.ms-excel',
'xm': 'audio/x-mod',
'xml': 'text/xml',
'xmz': 'audio/x-mod',
'xpi': 'application/x-xpinstall',
'xpm': 'image/x-xpixmap',
'xsit': 'text/xml',
'xsl': 'text/xml',
'xul': 'text/xul',
'xwd': 'image/x-xwindowdump',
'xyz': 'chemical/x-pdb',
'yz1': 'application/x-yz1',
'z': 'application/x-compress',
'zac': 'application/x-zaurus-zac',
'zip': 'application/zip',
'zsh': 'text/x-zsh'
}
def get_file_mime(name):
try:
sufix = os.path.splitext(name)[1][1:]
if sufix:
return MIME_MAP[sufix]
return None
except Exception, e:
return None
if __name__ == "__main__":
print MIME_MAP

28
api2/models.py Normal file
View File

@@ -0,0 +1,28 @@
import uuid
import hmac
from hashlib import sha1
from base.accounts import User
from django.db import models
class Token(models.Model):
"""
The default authorization token model.
"""
key = models.CharField(max_length=40, primary_key=True)
user = models.CharField(max_length=255, unique=True)
created = models.DateTimeField(auto_now_add=True)
def save(self, *args, **kwargs):
if not self.key:
self.key = self.generate_key()
return super(Token, self).save(*args, **kwargs)
def generate_key(self):
unique = str(uuid.uuid4())
return hmac.new(unique, digestmod=sha1).hexdigest()
def __unicode__(self):
return self.key

25
api2/serializers.py Normal file
View File

@@ -0,0 +1,25 @@
from rest_framework import serializers
from auth import authenticate
class AuthTokenSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
print username, password
if username and password:
user = authenticate(username=username, password=password)
if user:
if not user.is_active:
raise serializers.ValidationError('User account is disabled.')
attrs['user'] = user
return attrs
else:
raise serializers.ValidationError('Unable to login with provided credentials.')
else:
raise serializers.ValidationError('Must include "username" and "password"')

16
api2/tests.py Normal file
View File

@@ -0,0 +1,16 @@
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)

28
api2/urls.py Normal file
View File

@@ -0,0 +1,28 @@
from django.conf.urls.defaults import *
# from django.views.decorators.csrf import csrf_exempt
from views import *
urlpatterns = patterns('',
url(r'^ping/$', Ping.as_view()),
url(r'^auth/ping/$', AuthPing.as_view()),
url(r'^auth-token/', ObtainAuthToken.as_view()),
url(r'^account/info/$', Account.as_view()),
url(r'^repos/$', Repos.as_view()),
url(r'^repos/(?P<repo_id>[-0-9a-f]{36})/$', Repo.as_view()),
url(r'^repos/(?P<repo_id>[-0-9a-f]{36})/dirents/$', RepoDirents.as_view()),
url(r'^repos/(?P<repo_id>[-0-9a-f]{36})/filepath/$', RepoFilepath.as_view()),
url(r'^repos/(?P<repo_id>[-0-9a-f]{36})/dirs/(?P<dir_id>[0-9a-f]{40})/$', RepoDirs.as_view()),
url(r'^repos/(?P<repo_id>[-0-9a-f]{36})/files/(?P<file_id>[0-9a-f]{40})/$', RepoFiles.as_view()),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/fileops/delete/$', OpDeleteView.as_view()),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/fileops/rename/$', OpRenameView.as_view()),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/fileops/move/$', OpMoveView.as_view()),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/fileops/mkdir/$', OpMkdirView.as_view()),
url(r'^repos/(?P<repo_id>[-0-9-a-f]{36})/fileops/upload/$', OpUploadView.as_view()),
url(r'^starredfiles/', StarredFileView.as_view()),
)

714
api2/views.py Normal file
View File

@@ -0,0 +1,714 @@
# encoding: utf-8
import os
import stat
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 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
from pysearpc import SearpcError
from seaserv import seafserv_rpc, seafserv_threaded_rpc, \
get_personal_groups_by_user, \
get_group_repos, get_repo, check_permission, get_commits
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')
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 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]
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,
}
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')
print src_repo_id, dst_repo_id, src_dir, dst_dir, op, 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)

View File

@@ -132,6 +132,7 @@ INSTALLED_APPS = (
'seahub.share', 'seahub.share',
'seahub.subdomain', 'seahub.subdomain',
'seahub.api', 'seahub.api',
'api2',
) )
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (

View File

@@ -45,7 +45,7 @@ urlpatterns = patterns('',
(r'^repo/update_error/(?P<repo_id>[^/]+)/$', update_file_error), (r'^repo/update_error/(?P<repo_id>[^/]+)/$', update_file_error),
url(r'^repo/file_revisions/(?P<repo_id>[^/]+)/$', file_revisions, name='file_revisions'), url(r'^repo/file_revisions/(?P<repo_id>[^/]+)/$', file_revisions, name='file_revisions'),
url(r'^repo/text_diff/(?P<repo_id>[^/]+)/$', text_diff, name='text_diff'), url(r'^repo/text_diff/(?P<repo_id>[^/]+)/$', text_diff, name='text_diff'),
url(r'^repo/(?P<repo_id>[^/]{36})/$', RepoView.as_view(), name='repo'), url(r'^repo/(?P<repo_id>[-0-9a-f]{36})/$', RepoView.as_view(), name='repo'),
(r'^repo/history/(?P<repo_id>[^/]+)/$', repo_history), (r'^repo/history/(?P<repo_id>[^/]+)/$', repo_history),
(r'^repo/history/revert/(?P<repo_id>[^/]+)/$', repo_history_revert), (r'^repo/history/revert/(?P<repo_id>[^/]+)/$', repo_history_revert),
url(r'^repo/history/view/(?P<repo_id>[^/]+)/$', RepoHistoryView.as_view(), name='repo_history_view'), url(r'^repo/history/view/(?P<repo_id>[^/]+)/$', RepoHistoryView.as_view(), name='repo_history_view'),
@@ -78,6 +78,7 @@ urlpatterns = patterns('',
### Apps ### ### Apps ###
(r'^api/', include('api.urls')), (r'^api/', include('api.urls')),
(r'^api2/', include('api2.urls')),
(r'^avatar/', include('avatar.urls')), (r'^avatar/', include('avatar.urls')),
(r'^notification/', include('notifications.urls')), (r'^notification/', include('notifications.urls')),
url(r'^sys/notificationadmin/', notification_list, name='notification_list'), url(r'^sys/notificationadmin/', notification_list, name='notification_list'),