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:
parent
adbcc842dc
commit
18bc135a53
2
pytest.ini
Normal file
2
pytest.ini
Normal file
@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
addopts = -s -v
|
@ -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()),
|
||||
|
@ -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
|
||||
|
@ -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
26
seahub/api2/views_auth.py
Normal 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 {}
|
@ -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
64
tests/api/test_auth.py
Normal 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)
|
@ -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)
|
||||
|
@ -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/')
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user