1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-04-27 19:05:16 +00:00

[api] support logging out an accout from seafile clients

This commit is contained in:
Shuai Lin 2015-03-03 16:35:00 +08:00
parent adbcc842dc
commit 18bc135a53
12 changed files with 212 additions and 25 deletions

2
pytest.ini Normal file
View File

@ -0,0 +1,2 @@
[pytest]
addopts = -s -v

View File

@ -2,6 +2,7 @@ from django.conf.urls.defaults import *
from .views import *
from .views_misc import ServerInfoView
from .views_auth import LogoutDeviceView
urlpatterns = patterns('',
@ -9,6 +10,7 @@ urlpatterns = patterns('',
url(r'^auth/ping/$', AuthPing.as_view()),
url(r'^auth-token/', ObtainAuthToken.as_view()),
url(r'^server-info/$', ServerInfoView.as_view()),
url(r'^logout-device/$', LogoutDeviceView.as_view()),
# RESTful API
url(r'^accounts/$', Accounts.as_view(), name="accounts"),
@ -45,6 +47,7 @@ urlpatterns = patterns('',
url(r'^shared-links/$', SharedLinksView.as_view()),
url(r'^shared-files/$', SharedFilesView.as_view()),
url(r'^virtual-repos/$', VirtualRepos.as_view()),
url(r'^repo-tokens/$', RepoTokensView.as_view()),
url(r'^s/f/(?P<token>[a-f0-9]{10})/$', PrivateSharedFileView.as_view()),

View File

@ -514,6 +514,9 @@ def json_response(func):
@wraps(func)
def wrapped(*a, **kw):
result = func(*a, **kw)
return HttpResponse(json.dumps(result), status=200,
content_type=JSON_CONTENT_TYPE)
if isinstance(result, HttpResponse):
return result
else:
return HttpResponse(json.dumps(result), status=200,
content_type=JSON_CONTENT_TYPE)
return wrapped

View File

@ -5,6 +5,7 @@ import stat
import json
import datetime
import urllib2
import re
from urllib2 import unquote, quote
from PIL import Image
from StringIO import StringIO
@ -34,7 +35,8 @@ from .utils import is_repo_writable, is_repo_accessible, calculate_repo_info, \
api_error, get_file_size, prepare_starred_files, \
get_groups, get_group_and_contacts, prepare_events, \
get_person_msgs, api_group_check, get_email, get_timestamp, \
get_group_message_json, get_group_msgs, get_group_msgs_json, get_diff_details
get_group_message_json, get_group_msgs, get_group_msgs_json, get_diff_details, \
json_response
from seahub.avatar.templatetags.avatar_tags import api_avatar_url
from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url
from seahub.base.accounts import User
@ -176,7 +178,7 @@ class Accounts(APIView):
limit = int(request.GET.get('limit', '100'))
# reading scope user list
scope = request.GET.get('scope', None)
accounts_ldap = []
accounts_db = []
if scope:
@ -192,7 +194,7 @@ class Accounts(APIView):
accounts_ldap = seaserv.get_emailusers('LDAP', start, limit)
if len(accounts_ldap) == 0:
accounts_db = seaserv.get_emailusers('DB', start, limit)
accounts_json = []
for account in accounts_ldap:
accounts_json.append({'email': account.email, 'source' : 'LDAP'})
@ -2604,7 +2606,7 @@ class Groups(APIView):
group_json, replynum = get_groups(request.user.username)
res = {"groups": group_json, "replynum": replynum}
return Response(res)
def put(self, request, format=None):
# modified slightly from groups/views.py::group_list
"""
@ -3573,6 +3575,35 @@ class ThumbnailView(APIView):
except IOError as e:
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, str(e))
_REPO_ID_PATTERN = re.compile(r'[-0-9a-f]{36}')
class RepoTokensView(APIView):
authentication_classes = (TokenAuthentication, )
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle, )
@json_response
def get(self, request, format=None):
repos = request.GET.get('repos', None)
if not repos:
return api_error(status.HTTP_400_BAD_REQUEST, "You must specify libaries ids")
repos = [repo for repo in repos.split(',') if repo]
if any([not _REPO_ID_PATTERN.match(repo) for repo in repos]):
return api_error(status.HTTP_400_BAD_REQUEST, "Libraries ids are invalid")
if any([not seafile_api.check_repo_access_permission(
repo, request.user.username) for repo in repos]):
return api_error(status.HTTP_403_FORBIDDEN,
"You do not have permission to access those libraries")
tokens = {}
for repo in repos:
tokens[repo] = seafile_api.generate_repo_token(repo, request.user.username)
return tokens
#Following is only for debug
# from seahub.auth.decorators import login_required
# @login_required

26
seahub/api2/views_auth.py Normal file
View File

@ -0,0 +1,26 @@
from rest_framework import status
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
from seaserv import seafile_api
from seahub import settings
from seahub.api2.utils import json_response, api_error
from seahub.api2.authentication import TokenAuthentication
from seahub.api2.models import Token, TokenV2
class LogoutDeviceView(APIView):
"""Removes the api token of a device that has already logged in. If the device
is a desktop client, also remove all sync tokens of repos synced on that
client .
"""
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
@json_response
def post(self, request, format=None):
auth_token = request.auth
if isinstance(auth_token, TokenV2) and auth_token.is_desktop_client():
seafile_api.delete_repo_tokens_by_peer_id(request.user.username, auth_token.device_id)
auth_token.delete()
return {}

View File

@ -15,7 +15,7 @@ from tests.api.urls import TOKEN_URL, GROUPS_URL, ACCOUNTS_URL, REPOS_URL
class ApiTestBase(unittest.TestCase):
_token = None
_admin_token = None
username = USERNAME
password = PASSWORD
admin_username = ADMIN_USERNAME
@ -59,20 +59,24 @@ class ApiTestBase(unittest.TestCase):
@classmethod
def _req(cls, method, *args, **kwargs):
admin = kwargs.pop('admin', False)
if admin:
if cls._admin_token is None:
cls._admin_token = get_auth_token(ADMIN_USERNAME,
ADMIN_PASSWORD)
token = cls._admin_token
else:
if cls._token is None:
cls._token = get_auth_token(USERNAME, PASSWORD)
token = cls._token
use_token = kwargs.pop('use_token', True)
token = kwargs.pop('token', None)
if use_token and token is None:
admin = kwargs.pop('admin', False)
if admin:
if cls._admin_token is None:
cls._admin_token = get_auth_token(ADMIN_USERNAME,
ADMIN_PASSWORD)
token = cls._admin_token
else:
if cls._token is None:
cls._token = get_auth_token(USERNAME, PASSWORD)
token = cls._token
headers = kwargs.get('headers', {})
headers.setdefault('Authorization', 'Token ' + token)
kwargs['headers'] = headers
if use_token:
headers = kwargs.get('headers', {})
headers.setdefault('Authorization', 'Token ' + token)
kwargs['headers'] = headers
expected = kwargs.pop('expected', 200)
resp = requests.request(method, *args, **kwargs)

64
tests/api/test_auth.py Normal file
View File

@ -0,0 +1,64 @@
#coding: UTF-8
"""
Test auth related api, such as login/logout.
"""
import random
import re
from urllib import urlencode, quote
from tests.common.common import USERNAME, PASSWORD, SEAFILE_BASE_URL
from tests.common.utils import randstring, urljoin
from tests.api.urls import (
AUTH_PING_URL, TOKEN_URL, DOWNLOAD_REPO_URL, LOGOUT_DEVICE_URL
)
from tests.api.apitestbase import ApiTestBase
def fake_ccnet_id():
return randstring(length=40)
class AuthTest(ApiTestBase):
"""This tests involves creating/deleting api tokens, so for this test we use
a specific auth token so that it won't affect other test cases.
"""
def test_logout_device(self):
token = self._desktop_login()
self._do_auth_ping(token, expected=200)
with self.get_tmp_repo() as repo:
sync_token = self._clone_repo(token, repo.repo_id)
self._get_repo_info(sync_token, repo.repo_id)
self._logout(token)
self._do_auth_ping(token, expected=403)
# self._get_repo_info(sync_token, repo.repo_id, expected=400)
def _desktop_login(self):
data = {
'username': USERNAME,
'password': PASSWORD,
'platform': 'windows',
'device_id': fake_ccnet_id(),
'device_name': 'fake-device-name',
'client_version': '4.1.0',
'platform_version': '',
}
return self.post(TOKEN_URL, data=data, use_token=False).json()['token']
def _do_auth_ping(self, token, **kwargs):
return self.get(AUTH_PING_URL, token=token, **kwargs)
def _clone_repo(self, token, repo_id):
return self.get(DOWNLOAD_REPO_URL % repo_id, token=token).json()['token']
def _get_repo_info(self, sync_token, repo_id, **kwargs):
headers = {
'Seafile-Repo-Token': sync_token
}
url = urljoin(SEAFILE_BASE_URL,
'repo/%s/permission-check/?op=upload' % repo_id)
self.get(url, use_token=False, headers=headers, **kwargs)
def _logout(self, token):
self.post(LOGOUT_DEVICE_URL, token=token)

View File

@ -6,8 +6,11 @@ Test repos api.
import unittest
from tests.api.apitestbase import ApiTestBase
from tests.api.urls import REPOS_URL, DEFAULT_REPO_URL, VIRTUAL_REPOS_URL
from tests.api.urls import (
REPOS_URL, DEFAULT_REPO_URL, VIRTUAL_REPOS_URL, GET_REPO_TOKENS_URL
)
from tests.common.utils import apiurl, urljoin, randstring
from tests.common.common import USERNAME, PASSWORD, SEAFILE_BASE_URL
# TODO: all tests should be run on an encrypted repo
class ReposApiTest(ApiTestBase):
@ -156,3 +159,25 @@ class ReposApiTest(ApiTestBase):
#self.assertIsNotNone(repo['worktree'])
self.assertIsNotNone(repo['auto_sync'])
#self.assertIsNotNone(repo['relay_id'])
def test_generate_repo_tokens(self):
with self.get_tmp_repo() as ra:
with self.get_tmp_repo() as rb:
repo_ids = ','.join([ra.repo_id, rb.repo_id])
tokens = self.get(GET_REPO_TOKENS_URL + '?repos=%s' % repo_ids).json()
assert ra.repo_id in tokens
assert rb.repo_id in tokens
for repo_id, token in tokens.iteritems():
self._get_repo_info(token, repo_id)
def test_generate_repo_tokens_reject_invalid_params(self):
self.get(GET_REPO_TOKENS_URL, expected=400)
self.get(GET_REPO_TOKENS_URL + '?repos=badxxx', expected=400)
def _get_repo_info(self, sync_token, repo_id, **kwargs):
headers = {
'Seafile-Repo-Token': sync_token
}
url = urljoin(SEAFILE_BASE_URL,
'repo/%s/permission-check/?op=upload' % repo_id)
self.get(url, use_token=False, headers=headers, **kwargs)

View File

@ -12,6 +12,7 @@ AVATAR_BASE_URL = apiurl(u'/api2/avatars/')
REPOS_URL = apiurl('/api2/repos/')
DEFAULT_REPO_URL = apiurl('/api2/default-repo/')
VIRTUAL_REPOS_URL = apiurl('/api2/virtual-repos/')
GET_REPO_TOKENS_URL = apiurl('/api2/repo-tokens/')
GROUPS_URL = apiurl(u'/api2/groups/')
@ -29,5 +30,7 @@ F_URL = apiurl('/api2/f/')
S_F_URL = apiurl('/api2/s/f/')
LIST_GROUP_AND_CONTACTS_URL = apiurl('/api2/groupandcontacts/')
DOWNLOAD_REPO_URL = apiurl('api2/repos/%s/download-info/')
LOGOUT_DEVICE_URL = apiurl('api2/logout-device/')
SERVER_INFO_URL = apiurl('/api2/server-info/')

View File

@ -1,4 +1,5 @@
import os
import urlparse
BASE_URL = os.getenv('SEAHUB_TEST_BASE_URL', u'http://127.0.0.1:8000')
USERNAME = os.getenv('SEAHUB_TEST_USERNAME', u'test@seafiletest.com')
@ -10,3 +11,9 @@ if os.getenv('SEAHUB_TEST_IS_PRO', u'') == u'':
IS_PRO = False
else:
S_PRO = True
def get_seafile_http_sync_base_url():
u = urlparse.urlparse(BASE_URL)
return '{}://{}/seafhttp'.format(u.scheme, u.hostname)
SEAFILE_BASE_URL = get_seafile_http_sync_base_url()

View File

@ -13,3 +13,23 @@ cd "$SEAHUB_SRCDIR"
curl -L -o /tmp/phantomjs.tar.bz2 https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.7-linux-x86_64.tar.bz2
tar -C /tmp -xf /tmp/phantomjs.tar.bz2
sudo install -m 755 /tmp/phantomjs-1.9.7-linux-x86_64/bin/phantomjs /usr/bin/phantomjs
sudo apt-get install nginx
sudo mv /etc/nginx/sites-enabled/default /etc/nginx/default.backup
cat <<'EOF' >/tmp/seafile.conf
server {
listen 80;
server_name _ default_server;
location /seafhttp {
rewrite ^/fileserver(.*)$ $1 break;
proxy_pass http://127.0.0.1:8082;
client_max_body_size 0;
proxy_connect_timeout 36000s;
proxy_read_timeout 36000s;
}
}
EOF
sudo mv /tmp/seafile.conf /etc/nginx/sites-enabled/
sudo service nginx restart

View File

@ -23,13 +23,14 @@ if [[ ${TRAVIS} != "" ]]; then
set -x
fi
SCRIPT=$(readlink -f "$0")
SEAHUB_TESTSDIR=$(dirname "${SCRIPT}")
set -x
SEAHUB_TESTSDIR=$(python -c "import os; print os.path.dirname(os.path.realpath('$0'))")
SEAHUB_SRCDIR=$(dirname "${SEAHUB_TESTSDIR}")
local_settings_py=${SEAHUB_SRCDIR}/seahub/local_settings.py
export PYTHONPATH="/usr/local/lib/python2.7/site-packages:/usr/lib/python2.7/site-packages:${SEAHUB_SRCDIR}/thirdpart:${PYTHONPATH}"
cd "$SEAHUB_SRCDIR"
set +x
function init() {
###############################
@ -55,10 +56,8 @@ function start_seahub() {
function run_tests() {
set +e
cd tests
py.test $nose_opts
rvalue=$?
cd -
if [[ ${TRAVIS} != "" ]]; then
# On travis-ci, dump seahub logs when test finished
for logfile in /tmp/seahub*.log; do