mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-14 14:21:23 +00:00
Added remote wipe option when unlinking a device.
This commit is contained in:
@@ -7,7 +7,7 @@ from rest_framework.exceptions import APIException
|
|||||||
import seaserv
|
import seaserv
|
||||||
from seahub.base.accounts import User
|
from seahub.base.accounts import User
|
||||||
from seahub.constants import GUEST_USER
|
from seahub.constants import GUEST_USER
|
||||||
from seahub.api2.models import Token, TokenV2
|
from seahub.api2.models import Token, TokenV2, WipedDevice
|
||||||
from seahub.api2.utils import get_client_ip
|
from seahub.api2.utils import get_client_ip
|
||||||
from seahub.utils import within_time_range
|
from seahub.utils import within_time_range
|
||||||
try:
|
try:
|
||||||
@@ -28,6 +28,8 @@ class AuthenticationFailed(APIException):
|
|||||||
def __init__(self, detail=None):
|
def __init__(self, detail=None):
|
||||||
self.detail = detail or self.default_detail
|
self.detail = detail or self.default_detail
|
||||||
|
|
||||||
|
class DeviceRemoteWipedException(AuthenticationFailed):
|
||||||
|
pass
|
||||||
|
|
||||||
class TokenAuthentication(BaseAuthentication):
|
class TokenAuthentication(BaseAuthentication):
|
||||||
"""
|
"""
|
||||||
@@ -98,7 +100,15 @@ class TokenAuthentication(BaseAuthentication):
|
|||||||
try:
|
try:
|
||||||
token = TokenV2.objects.get(key=key)
|
token = TokenV2.objects.get(key=key)
|
||||||
except TokenV2.DoesNotExist:
|
except TokenV2.DoesNotExist:
|
||||||
return None # Continue authentication in token v1
|
try:
|
||||||
|
token = WipedDevice.objects.get(key=key)
|
||||||
|
except WipedDevice.DoesNotExist:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise DeviceRemoteWipedException('Device set to be remote wiped')
|
||||||
|
|
||||||
|
# Continue authentication in token v1
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = User.objects.get(email=token.user)
|
user = User.objects.get(email=token.user)
|
||||||
|
25
seahub/api2/base.py
Normal file
25
seahub/api2/base.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#coding: UTF-8
|
||||||
|
|
||||||
|
from rest_framework.views import APIView as RestFrameworkAPIView
|
||||||
|
|
||||||
|
from seahub.api2.authentication import DeviceRemoteWipedException
|
||||||
|
|
||||||
|
class APIView(RestFrameworkAPIView):
|
||||||
|
"""
|
||||||
|
Subclass restframework's APIView to implement some custom feature like
|
||||||
|
adding a `X-Seafile-Wiped` header if the current client device has been
|
||||||
|
marked to be remote wiped by the user.
|
||||||
|
"""
|
||||||
|
def __init__(self, *a, **kw):
|
||||||
|
super(APIView, self).__init__(*a, **kw)
|
||||||
|
self._seafile_exc = None
|
||||||
|
|
||||||
|
def handle_exception(self, exc):
|
||||||
|
self._seafile_exc = exc
|
||||||
|
return super(APIView, self).handle_exception(exc)
|
||||||
|
|
||||||
|
def dispatch(self, *a, **kw):
|
||||||
|
response = super(APIView, self).dispatch(*a, **kw)
|
||||||
|
if self._seafile_exc and isinstance(self._seafile_exc, DeviceRemoteWipedException):
|
||||||
|
response['X-Seafile-Wiped'] = 'true'
|
||||||
|
return response
|
@@ -1,7 +1,8 @@
|
|||||||
import uuid
|
import uuid
|
||||||
import hmac
|
import hmac
|
||||||
|
import datetime
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
|
|
||||||
from seahub.base.fields import LowerCaseCharField
|
from seahub.base.fields import LowerCaseCharField
|
||||||
|
|
||||||
@@ -96,11 +97,19 @@ class TokenV2Manager(models.Manager):
|
|||||||
last_login_ip=last_login_ip)
|
last_login_ip=last_login_ip)
|
||||||
token.save()
|
token.save()
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
|
||||||
def delete_device_token(self, username, platform, device_id):
|
def delete_device_token(self, username, platform, device_id):
|
||||||
super(TokenV2Manager, self).filter(user=username, platform=platform, device_id=device_id).delete()
|
super(TokenV2Manager, self).filter(user=username, platform=platform, device_id=device_id).delete()
|
||||||
|
|
||||||
|
def mark_device_to_be_remote_wiped(self, username, platform, device_id):
|
||||||
|
token = self._get_token_by_user_device(username, platform, device_id)
|
||||||
|
if not token:
|
||||||
|
return
|
||||||
|
with transaction.atomic():
|
||||||
|
wiped_device = WipedDevice(key=token.key)
|
||||||
|
wiped_device.save()
|
||||||
|
token.delete()
|
||||||
|
|
||||||
class TokenV2(models.Model):
|
class TokenV2(models.Model):
|
||||||
"""
|
"""
|
||||||
@@ -162,3 +171,8 @@ class TokenV2(models.Model):
|
|||||||
platform_version=self.platform_version,
|
platform_version=self.platform_version,
|
||||||
last_accessed=self.last_accessed,
|
last_accessed=self.last_accessed,
|
||||||
last_login_ip=self.last_login_ip)
|
last_login_ip=self.last_login_ip)
|
||||||
|
|
||||||
|
class WipedDevice(models.Model):
|
||||||
|
key = models.CharField(max_length=40, primary_key=True)
|
||||||
|
|
||||||
|
wiped_at = models.DateTimeField(auto_now=True)
|
||||||
|
@@ -23,6 +23,7 @@ urlpatterns = patterns('',
|
|||||||
url(r'^server-info/$', ServerInfoView.as_view()),
|
url(r'^server-info/$', ServerInfoView.as_view()),
|
||||||
url(r'^logout-device/$', LogoutDeviceView.as_view()),
|
url(r'^logout-device/$', LogoutDeviceView.as_view()),
|
||||||
url(r'^client-login/$', ClientLoginTokenView.as_view()),
|
url(r'^client-login/$', ClientLoginTokenView.as_view()),
|
||||||
|
url(r'^device-wiped/$', RemoteWipeReportView.as_view()),
|
||||||
|
|
||||||
# RESTful API
|
# RESTful API
|
||||||
url(r'^accounts/$', Accounts.as_view(), name="accounts"),
|
url(r'^accounts/$', Accounts.as_view(), name="accounts"),
|
||||||
|
@@ -16,7 +16,6 @@ from rest_framework.authentication import SessionAuthentication
|
|||||||
from rest_framework.permissions import IsAuthenticated, IsAdminUser
|
from rest_framework.permissions import IsAuthenticated, IsAdminUser
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
|
||||||
|
|
||||||
from django.contrib.auth.hashers import check_password
|
from django.contrib.auth.hashers import check_password
|
||||||
from django.contrib.sites.models import RequestSite
|
from django.contrib.sites.models import RequestSite
|
||||||
@@ -40,6 +39,7 @@ from .utils import get_diff_details, \
|
|||||||
api_repo_user_folder_perm_check, api_repo_setting_permission_check, \
|
api_repo_user_folder_perm_check, api_repo_setting_permission_check, \
|
||||||
api_repo_group_folder_perm_check
|
api_repo_group_folder_perm_check
|
||||||
|
|
||||||
|
from seahub.api2.base import APIView
|
||||||
from seahub.avatar.templatetags.avatar_tags import api_avatar_url, avatar
|
from seahub.avatar.templatetags.avatar_tags import api_avatar_url, avatar
|
||||||
from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url, \
|
from seahub.avatar.templatetags.group_avatar_tags import api_grp_avatar_url, \
|
||||||
grp_avatar
|
grp_avatar
|
||||||
@@ -1914,6 +1914,7 @@ class DevicesView(APIView):
|
|||||||
|
|
||||||
platform = request.data.get('platform', '')
|
platform = request.data.get('platform', '')
|
||||||
device_id = request.data.get('device_id', '')
|
device_id = request.data.get('device_id', '')
|
||||||
|
remote_wipe = request.data.get('wipe_device', '')
|
||||||
|
|
||||||
if not platform:
|
if not platform:
|
||||||
error_msg = 'platform invalid.'
|
error_msg = 'platform invalid.'
|
||||||
@@ -1924,7 +1925,10 @@ class DevicesView(APIView):
|
|||||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
do_unlink_device(request.user.username, platform, device_id)
|
do_unlink_device(request.user.username,
|
||||||
|
platform,
|
||||||
|
device_id,
|
||||||
|
remote_wipe=remote_wipe)
|
||||||
except SearpcError as e:
|
except SearpcError as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
error_msg = 'Internal Server Error'
|
error_msg = 'Internal Server Error'
|
||||||
@@ -4534,3 +4538,20 @@ class RepoGroupFolderPerm(APIView):
|
|||||||
logger.error(e)
|
logger.error(e)
|
||||||
error_msg = 'Internal Server Error'
|
error_msg = 'Internal Server Error'
|
||||||
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
|
||||||
|
|
||||||
|
class RemoteWipeReportView(APIView):
|
||||||
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
|
@json_response
|
||||||
|
def post(self, request):
|
||||||
|
from seahub.api2.models import WipedDevice
|
||||||
|
token = request.POST.get('token', '')
|
||||||
|
if not token or len(token) != 40:
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, "device token is missing")
|
||||||
|
try:
|
||||||
|
entry = WipedDevice.objects.get(key=token)
|
||||||
|
entry.delete()
|
||||||
|
except WipedDevice.DoesNotExist:
|
||||||
|
return api_error(status.HTTP_400_BAD_REQUEST, "invalid device token")
|
||||||
|
|
||||||
|
return {}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.views import APIView
|
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
|
|
||||||
from seaserv import seafile_api
|
from seaserv import seafile_api
|
||||||
from seahub import settings
|
from seahub import settings
|
||||||
|
from seahub.api2.base import APIView
|
||||||
from seahub.api2.throttling import AnonRateThrottle, UserRateThrottle
|
from seahub.api2.throttling import AnonRateThrottle, UserRateThrottle
|
||||||
from seahub.api2.utils import json_response, api_error
|
from seahub.api2.utils import json_response, api_error
|
||||||
from seahub.api2.authentication import TokenAuthentication
|
from seahub.api2.authentication import TokenAuthentication
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
from rest_framework.views import APIView
|
from seahub.api2.base import APIView
|
||||||
|
|
||||||
from seahub.api2.utils import json_response, is_seafile_pro
|
from seahub.api2.utils import json_response, is_seafile_pro
|
||||||
from seahub import settings
|
from seahub import settings
|
||||||
from seahub.utils import HAS_OFFICE_CONVERTER, HAS_FILE_SEARCH
|
from seahub.utils import HAS_OFFICE_CONVERTER, HAS_FILE_SEARCH
|
||||||
|
@@ -68,7 +68,7 @@ def get_user_synced_repo_infos(username):
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def do_unlink_device(username, platform, device_id):
|
def do_unlink_device(username, platform, device_id, remote_wipe=False):
|
||||||
if platform in DESKTOP_PLATFORMS:
|
if platform in DESKTOP_PLATFORMS:
|
||||||
# For desktop client, we also remove the sync tokens
|
# For desktop client, we also remove the sync tokens
|
||||||
msg = 'failed to delete_repo_tokens_by_peer_id'
|
msg = 'failed to delete_repo_tokens_by_peer_id'
|
||||||
@@ -80,4 +80,7 @@ def do_unlink_device(username, platform, device_id):
|
|||||||
logger.exception(msg)
|
logger.exception(msg)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
TokenV2.objects.delete_device_token(username, platform, device_id)
|
if remote_wipe:
|
||||||
|
TokenV2.objects.mark_device_to_be_remote_wiped(username, platform, device_id)
|
||||||
|
else:
|
||||||
|
TokenV2.objects.delete_device_token(username, platform, device_id)
|
||||||
|
@@ -11,6 +11,9 @@ define([
|
|||||||
'platform': this.get('platform'),
|
'platform': this.get('platform'),
|
||||||
'device_id': this.get('device_id')
|
'device_id': this.get('device_id')
|
||||||
};
|
};
|
||||||
|
if (options.wipe_device) {
|
||||||
|
data['wipe_device'] = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: Common.getUrl({name: 'devices'}),
|
url: Common.getUrl({name: 'devices'}),
|
||||||
|
@@ -73,9 +73,13 @@ define([
|
|||||||
device_name = this.model.get('device_name');
|
device_name = this.model.get('device_name');
|
||||||
var title = gettext('Unlink device');
|
var title = gettext('Unlink device');
|
||||||
var content = gettext('Are you sure you want to unlink this device?');
|
var content = gettext('Are you sure you want to unlink this device?');
|
||||||
|
var extraOption = gettext('Delete files from this device the next time it comes online.');
|
||||||
|
|
||||||
var yesCallback = function () {
|
var yesCallback = function (wipe_device) {
|
||||||
|
console.log('wipe_device = ' + wipe_device);
|
||||||
_this.model.unlink({
|
_this.model.unlink({
|
||||||
|
wipe_device: wipe_device,
|
||||||
|
|
||||||
success: function() {
|
success: function() {
|
||||||
_this.remove();
|
_this.remove();
|
||||||
|
|
||||||
@@ -92,7 +96,7 @@ define([
|
|||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
Common.showConfirm(title, content, yesCallback);
|
Common.showConfirmWithExtraOption(title, content, extraOption, yesCallback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user