mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-06 01:12:03 +00:00
Rewrite 2fa, do not import models in package level
This commit is contained in:
@@ -9,7 +9,7 @@ from seahub.api2.base import APIView
|
|||||||
from seahub.api2.throttling import UserRateThrottle
|
from seahub.api2.throttling import 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
|
||||||
from seahub.two_factor import devices_for_user
|
from seahub.two_factor.models import devices_for_user
|
||||||
|
|
||||||
|
|
||||||
class TwoFactorAuthView(APIView):
|
class TwoFactorAuthView(APIView):
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
# Copyright (c) 2012-2016 Seafile Ltd.
|
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||||
import datetime
|
import datetime
|
||||||
|
from importlib import import_module
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.utils.importlib import import_module
|
|
||||||
|
|
||||||
from seahub.auth.signals import user_logged_in
|
from seahub.auth.signals import user_logged_in
|
||||||
from seahub.utils import is_org_context
|
|
||||||
|
|
||||||
from constance import config
|
from constance import config
|
||||||
|
|
||||||
@@ -101,6 +101,8 @@ def logout(request):
|
|||||||
if hasattr(request, 'user'):
|
if hasattr(request, 'user'):
|
||||||
from seahub.base.accounts import User
|
from seahub.base.accounts import User
|
||||||
if isinstance(request.user, User):
|
if isinstance(request.user, User):
|
||||||
|
# Do not directly/indirectly import models in package root level.
|
||||||
|
from seahub.utils import is_org_context
|
||||||
if is_org_context(request):
|
if is_org_context(request):
|
||||||
org_id = request.user.org.org_id
|
org_id = request.user.org.org_id
|
||||||
request.user.remove_org_repo_passwds(org_id)
|
request.user.remove_org_repo_passwds(org_id)
|
||||||
|
@@ -88,8 +88,7 @@ def edit_profile(request):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if has_two_factor_auth():
|
if has_two_factor_auth():
|
||||||
from seahub.two_factor.models import StaticDevice
|
from seahub.two_factor.models import StaticDevice, default_device
|
||||||
from seahub.two_factor.utils import default_device
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
backup_tokens = StaticDevice.objects.get(
|
backup_tokens = StaticDevice.objects.get(
|
||||||
|
@@ -1,9 +1,4 @@
|
|||||||
# Copyright (c) 2012-2016 Seafile Ltd.
|
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||||
import sys
|
|
||||||
|
|
||||||
from seahub.auth.signals import user_logged_in
|
|
||||||
from seahub.two_factor.models import (StaticDevice, TOTPDevice,
|
|
||||||
PhoneDevice)
|
|
||||||
|
|
||||||
############################## django_otp ##############################
|
############################## django_otp ##############################
|
||||||
DEVICE_ID_SESSION_KEY = 'otp_device_id'
|
DEVICE_ID_SESSION_KEY = 'otp_device_id'
|
||||||
@@ -29,82 +24,3 @@ def login(request, device):
|
|||||||
if (user is not None) and (device is not None) and (device.user == user.email):
|
if (user is not None) and (device is not None) and (device.user == user.email):
|
||||||
request.session[DEVICE_ID_SESSION_KEY] = device.persistent_id
|
request.session[DEVICE_ID_SESSION_KEY] = device.persistent_id
|
||||||
request.user.otp_device = device
|
request.user.otp_device = device
|
||||||
|
|
||||||
|
|
||||||
def _handle_auth_login(sender, request, user, **kwargs):
|
|
||||||
"""
|
|
||||||
Automatically persists an OTP device that was set by an OTP-aware
|
|
||||||
AuthenticationForm.
|
|
||||||
"""
|
|
||||||
if hasattr(user, 'otp_device'):
|
|
||||||
login(request, user.otp_device)
|
|
||||||
|
|
||||||
user_logged_in.connect(_handle_auth_login)
|
|
||||||
|
|
||||||
def match_token(user, token):
|
|
||||||
"""
|
|
||||||
Attempts to verify a :term:`token` on every device attached to the given
|
|
||||||
user until one of them succeeds. When possible, you should prefer to verify
|
|
||||||
tokens against specific devices.
|
|
||||||
|
|
||||||
:param user: The user supplying the token.
|
|
||||||
:type user: :class:`~django.contrib.auth.models.User`
|
|
||||||
|
|
||||||
:param string token: An OTP token to verify.
|
|
||||||
|
|
||||||
:returns: The device that accepted ``token``, if any.
|
|
||||||
:rtype: :class:`~django_otp.models.Device` or ``None``
|
|
||||||
"""
|
|
||||||
matches = (d for d in devices_for_user(user) if d.verify_token(token))
|
|
||||||
|
|
||||||
return next(matches, None)
|
|
||||||
|
|
||||||
def devices_for_user(user):
|
|
||||||
"""
|
|
||||||
Return an iterable of all devices registered to the given user.
|
|
||||||
|
|
||||||
Returns an empty iterable for anonymous users.
|
|
||||||
|
|
||||||
:param user: standard or custom user object.
|
|
||||||
:type user: :class:`~seahub.auth.models.User`
|
|
||||||
|
|
||||||
:rtype: iterable
|
|
||||||
"""
|
|
||||||
if user.is_anonymous():
|
|
||||||
return
|
|
||||||
|
|
||||||
for model in TOTPDevice, PhoneDevice, StaticDevice:
|
|
||||||
device = model.objects.device_for_user(user.username)
|
|
||||||
if device:
|
|
||||||
yield device
|
|
||||||
|
|
||||||
def user_has_device(user):
|
|
||||||
"""
|
|
||||||
Return ``True`` if the user has at least one device.
|
|
||||||
|
|
||||||
Returns ``False`` for anonymous users.
|
|
||||||
|
|
||||||
:param user: standard or custom user object.
|
|
||||||
:type user: :class:`~seahub.auth.models.User`
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
next(devices_for_user(user))
|
|
||||||
except StopIteration:
|
|
||||||
has_device = False
|
|
||||||
else:
|
|
||||||
has_device = True
|
|
||||||
|
|
||||||
return has_device
|
|
||||||
|
|
||||||
def import_class(path):
|
|
||||||
"""
|
|
||||||
Imports a class based on a full Python path ('pkg.pkg.mod.Class'). This
|
|
||||||
does not trap any exceptions if the path is not valid.
|
|
||||||
"""
|
|
||||||
module, name = path.rsplit('.', 1)
|
|
||||||
__import__(module)
|
|
||||||
mod = sys.modules[module]
|
|
||||||
cls = getattr(mod, name)
|
|
||||||
|
|
||||||
return cls
|
|
||||||
|
@@ -3,7 +3,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
|
|||||||
|
|
||||||
|
|
||||||
from seahub.auth.decorators import user_passes_test
|
from seahub.auth.decorators import user_passes_test
|
||||||
from seahub.two_factor import user_has_device
|
from seahub.two_factor.models import user_has_device
|
||||||
from seahub.two_factor.conf import settings
|
from seahub.two_factor.conf import settings
|
||||||
|
|
||||||
def otp_required(view=None, redirect_field_name='next', login_url=None, if_configured=False):
|
def otp_required(view=None, redirect_field_name='next', login_url=None, if_configured=False):
|
||||||
|
@@ -6,11 +6,9 @@ from django import forms
|
|||||||
from django.forms import ModelForm, Form
|
from django.forms import ModelForm, Form
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from seahub.two_factor import devices_for_user, match_token
|
|
||||||
from seahub.two_factor.oath import totp
|
from seahub.two_factor.oath import totp
|
||||||
from seahub.two_factor.models import (Device, TOTPDevice, StaticDevice,
|
from seahub.two_factor.models import (Device, TOTPDevice, StaticDevice,
|
||||||
PhoneDevice)
|
PhoneDevice, devices_for_user, match_token)
|
||||||
|
|
||||||
|
|
||||||
from .models import get_available_methods
|
from .models import get_available_methods
|
||||||
from .utils import totp_digits
|
from .utils import totp_digits
|
||||||
|
@@ -7,7 +7,7 @@ except ImportError:
|
|||||||
else:
|
else:
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
from seahub.two_factor import devices_for_user
|
from seahub.two_factor.models import devices_for_user
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
from ...utils import default_device
|
from seahub.two_factor.models import default_device
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
|
@@ -3,3 +3,81 @@ from seahub.two_factor.models.base import Device, get_available_methods
|
|||||||
from seahub.two_factor.models.totp import TOTPDevice
|
from seahub.two_factor.models.totp import TOTPDevice
|
||||||
from seahub.two_factor.models.phone import PhoneDevice
|
from seahub.two_factor.models.phone import PhoneDevice
|
||||||
from seahub.two_factor.models.static import StaticDevice, StaticToken
|
from seahub.two_factor.models.static import StaticDevice, StaticToken
|
||||||
|
|
||||||
|
def devices_for_user(user):
|
||||||
|
"""
|
||||||
|
Return an iterable of all devices registered to the given user.
|
||||||
|
|
||||||
|
Returns an empty iterable for anonymous users.
|
||||||
|
|
||||||
|
:param user: standard or custom user object.
|
||||||
|
:type user: :class:`~seahub.auth.models.User`
|
||||||
|
|
||||||
|
:rtype: iterable
|
||||||
|
"""
|
||||||
|
if user.is_anonymous():
|
||||||
|
return
|
||||||
|
|
||||||
|
for model in TOTPDevice, PhoneDevice, StaticDevice:
|
||||||
|
device = model.objects.device_for_user(user.username)
|
||||||
|
if device:
|
||||||
|
yield device
|
||||||
|
|
||||||
|
def user_has_device(user):
|
||||||
|
"""
|
||||||
|
Return ``True`` if the user has at least one device.
|
||||||
|
|
||||||
|
Returns ``False`` for anonymous users.
|
||||||
|
|
||||||
|
:param user: standard or custom user object.
|
||||||
|
:type user: :class:`~seahub.auth.models.User`
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
next(devices_for_user(user))
|
||||||
|
except StopIteration:
|
||||||
|
has_device = False
|
||||||
|
else:
|
||||||
|
has_device = True
|
||||||
|
|
||||||
|
return has_device
|
||||||
|
|
||||||
|
def default_device(user):
|
||||||
|
if not user or user.is_anonymous():
|
||||||
|
return
|
||||||
|
|
||||||
|
for device in devices_for_user(user):
|
||||||
|
if device:
|
||||||
|
return device
|
||||||
|
|
||||||
|
def match_token(user, token):
|
||||||
|
"""
|
||||||
|
Attempts to verify a :term:`token` on every device attached to the given
|
||||||
|
user until one of them succeeds. When possible, you should prefer to verify
|
||||||
|
tokens against specific devices.
|
||||||
|
|
||||||
|
:param user: The user supplying the token.
|
||||||
|
:type user: :class:`~django.contrib.auth.models.User`
|
||||||
|
|
||||||
|
:param string token: An OTP token to verify.
|
||||||
|
|
||||||
|
:returns: The device that accepted ``token``, if any.
|
||||||
|
:rtype: :class:`~django_otp.models.Device` or ``None``
|
||||||
|
"""
|
||||||
|
matches = (d for d in devices_for_user(user) if d.verify_token(token))
|
||||||
|
|
||||||
|
return next(matches, None)
|
||||||
|
|
||||||
|
########## handle signals
|
||||||
|
from django.dispatch import receiver
|
||||||
|
from seahub.auth.signals import user_logged_in
|
||||||
|
from seahub.two_factor import login
|
||||||
|
|
||||||
|
@receiver(user_logged_in)
|
||||||
|
def _handle_auth_login(sender, request, user, **kwargs):
|
||||||
|
"""
|
||||||
|
Automatically persists an OTP device that was set by an OTP-aware
|
||||||
|
AuthenticationForm.
|
||||||
|
"""
|
||||||
|
if hasattr(user, 'otp_device'):
|
||||||
|
login(request, user.otp_device)
|
||||||
|
@@ -75,7 +75,7 @@ class Device(models.Model):
|
|||||||
|
|
||||||
device == Device.from_persistent_id(device.persistent_id)
|
device == Device.from_persistent_id(device.persistent_id)
|
||||||
"""
|
"""
|
||||||
from seahub.two_factor import import_class
|
from seahub.two_factor.utils import import_class
|
||||||
|
|
||||||
try:
|
try:
|
||||||
device_type, device_id = path.rsplit('/', 1)
|
device_type, device_id = path.rsplit('/', 1)
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2012-2016 Seafile Ltd.
|
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import sys
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
from os import urandom
|
from os import urandom
|
||||||
|
|
||||||
@@ -13,17 +14,6 @@ from django.conf import settings
|
|||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
|
|
||||||
def default_device(user):
|
|
||||||
if not user or user.is_anonymous():
|
|
||||||
return
|
|
||||||
|
|
||||||
# to avoid circular import
|
|
||||||
from seahub.two_factor import devices_for_user
|
|
||||||
for device in devices_for_user(user):
|
|
||||||
if device:
|
|
||||||
return device
|
|
||||||
|
|
||||||
def get_otpauth_url(accountname, secret, issuer=None, digits=None):
|
def get_otpauth_url(accountname, secret, issuer=None, digits=None):
|
||||||
# For a complete run-through of all the parameters, have a look at the
|
# For a complete run-through of all the parameters, have a look at the
|
||||||
# specs at:
|
# specs at:
|
||||||
@@ -116,3 +106,18 @@ def random_hex(length=20):
|
|||||||
:rtype: str
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
return hexlify(urandom(length))
|
return hexlify(urandom(length))
|
||||||
|
|
||||||
|
def import_class(path):
|
||||||
|
"""
|
||||||
|
Imports a class based on a full Python path ('pkg.pkg.mod.Class'). This
|
||||||
|
does not trap any exceptions if the path is not valid.
|
||||||
|
"""
|
||||||
|
module, name = path.rsplit('.', 1)
|
||||||
|
__import__(module)
|
||||||
|
mod = sys.modules[module]
|
||||||
|
cls = getattr(mod, name)
|
||||||
|
|
||||||
|
return cls
|
||||||
|
|
||||||
|
# # Move here to avoid circular import
|
||||||
|
# from seahub.two_factor.models import (StaticDevice, TOTPDevice, PhoneDevice)
|
||||||
|
@@ -32,13 +32,12 @@ from seahub.auth.forms import AuthenticationForm
|
|||||||
|
|
||||||
from seahub.two_factor import login as two_factor_login
|
from seahub.two_factor import login as two_factor_login
|
||||||
from seahub.two_factor.decorators import otp_required
|
from seahub.two_factor.decorators import otp_required
|
||||||
from seahub.two_factor.models import (StaticToken, StaticDevice,
|
from seahub.two_factor.models import (StaticDevice, PhoneDevice,
|
||||||
PhoneDevice, get_available_methods)
|
get_available_methods, default_device)
|
||||||
from seahub.two_factor.utils import random_hex, totp_digits
|
from seahub.two_factor.utils import random_hex, totp_digits, get_otpauth_url
|
||||||
|
|
||||||
from seahub.two_factor.forms import (MethodForm, TOTPDeviceForm,
|
from seahub.two_factor.forms import (MethodForm, TOTPDeviceForm,
|
||||||
PhoneNumberForm, DeviceValidationForm)
|
PhoneNumberForm, DeviceValidationForm)
|
||||||
from seahub.two_factor.utils import get_otpauth_url, default_device
|
|
||||||
from seahub.two_factor.views.utils import (class_view_decorator,
|
from seahub.two_factor.views.utils import (class_view_decorator,
|
||||||
CheckTwoFactorEnabledMixin,
|
CheckTwoFactorEnabledMixin,
|
||||||
IdempotentSessionWizardView)
|
IdempotentSessionWizardView)
|
||||||
|
@@ -28,11 +28,10 @@ from seahub.utils.ip import get_remote_ip
|
|||||||
from seahub.profile.models import Profile
|
from seahub.profile.models import Profile
|
||||||
|
|
||||||
from seahub.two_factor import login as two_factor_login
|
from seahub.two_factor import login as two_factor_login
|
||||||
from seahub.two_factor import user_has_device
|
from seahub.two_factor.models import (StaticDevice, TOTPDevice, default_device,
|
||||||
from seahub.two_factor.models import StaticDevice, TOTPDevice, PhoneDevice
|
user_has_device)
|
||||||
|
|
||||||
from seahub.two_factor.forms import TOTPTokenAuthForm, BackupTokenAuthForm, AuthenticationTokenForm
|
from seahub.two_factor.forms import TOTPTokenAuthForm, BackupTokenAuthForm, AuthenticationTokenForm
|
||||||
from seahub.two_factor.utils import default_device
|
|
||||||
from seahub.two_factor.views.utils import class_view_decorator
|
from seahub.two_factor.views.utils import class_view_decorator
|
||||||
|
|
||||||
from seahub.utils.auth import get_login_bg_image_path
|
from seahub.utils.auth import get_login_bg_image_path
|
||||||
|
@@ -10,7 +10,7 @@ from seahub.auth import REDIRECT_FIELD_NAME
|
|||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
from ..utils import default_device
|
from seahub.two_factor.models import default_device
|
||||||
|
|
||||||
|
|
||||||
class OTPRequiredMixin(object):
|
class OTPRequiredMixin(object):
|
||||||
|
@@ -8,10 +8,9 @@ from django.views.generic import TemplateView, FormView
|
|||||||
|
|
||||||
from seahub.auth.decorators import login_required
|
from seahub.auth.decorators import login_required
|
||||||
|
|
||||||
from seahub.two_factor import user_has_device, devices_for_user
|
|
||||||
from seahub.two_factor.forms import DisableForm
|
from seahub.two_factor.forms import DisableForm
|
||||||
from seahub.two_factor.models import StaticDevice
|
from seahub.two_factor.models import (StaticDevice, devices_for_user,
|
||||||
from seahub.two_factor.utils import default_device
|
user_has_device, default_device)
|
||||||
from seahub.two_factor.views.utils import class_view_decorator, CheckTwoFactorEnabledMixin
|
from seahub.two_factor.views.utils import class_view_decorator, CheckTwoFactorEnabledMixin
|
||||||
|
|
||||||
|
|
||||||
|
@@ -44,7 +44,7 @@ from seahub.invitations.models import Invitation
|
|||||||
from seahub.role_permissions.utils import get_available_roles, \
|
from seahub.role_permissions.utils import get_available_roles, \
|
||||||
get_available_admin_roles
|
get_available_admin_roles
|
||||||
from seahub.role_permissions.models import AdminRole
|
from seahub.role_permissions.models import AdminRole
|
||||||
from seahub.two_factor.utils import default_device
|
from seahub.two_factor.models import default_device
|
||||||
from seahub.utils import IS_EMAIL_CONFIGURED, string2list, is_valid_username, \
|
from seahub.utils import IS_EMAIL_CONFIGURED, string2list, is_valid_username, \
|
||||||
is_pro_version, send_html_email, get_user_traffic_list, get_server_id, \
|
is_pro_version, send_html_email, get_user_traffic_list, get_server_id, \
|
||||||
handle_virus_record, get_virus_record_by_id, \
|
handle_virus_record, get_virus_record_by_id, \
|
||||||
|
@@ -3,6 +3,7 @@ import pytest
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
from seahub.test_utils import BaseTestCase
|
from seahub.test_utils import BaseTestCase
|
||||||
|
from seahub.two_factor.models import TOTPDevice, devices_for_user
|
||||||
|
|
||||||
TRAVIS = 'TRAVIS' in os.environ
|
TRAVIS = 'TRAVIS' in os.environ
|
||||||
|
|
||||||
@@ -13,12 +14,9 @@ class TwoFactorAuthViewTest(BaseTestCase):
|
|||||||
self.login_as(self.admin)
|
self.login_as(self.admin)
|
||||||
|
|
||||||
def test_can_disable_two_factor_auth(self):
|
def test_can_disable_two_factor_auth(self):
|
||||||
from seahub.two_factor.models import (StaticDevice, TOTPDevice,
|
|
||||||
PhoneDevice)
|
|
||||||
totp = TOTPDevice(user=self.admin, name="", confirmed=1)
|
totp = TOTPDevice(user=self.admin, name="", confirmed=1)
|
||||||
totp.save()
|
totp.save()
|
||||||
|
|
||||||
from seahub.two_factor import devices_for_user
|
|
||||||
devices = devices_for_user(self.admin)
|
devices = devices_for_user(self.admin)
|
||||||
i = 0
|
i = 0
|
||||||
for device in devices_for_user(self.admin):
|
for device in devices_for_user(self.admin):
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from constance import config
|
from constance import config
|
||||||
|
|
||||||
from seahub.two_factor.models import StaticDevice
|
from seahub.two_factor.models import StaticDevice, user_has_device
|
||||||
from seahub.two_factor import user_has_device
|
|
||||||
from seahub.test_utils import BaseTestCase
|
from seahub.test_utils import BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user