mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-05 08:53:14 +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.utils import json_response, api_error
|
||||
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):
|
||||
|
@@ -1,12 +1,12 @@
|
||||
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||
import datetime
|
||||
from importlib import import_module
|
||||
from warnings import warn
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.importlib import import_module
|
||||
|
||||
from seahub.auth.signals import user_logged_in
|
||||
from seahub.utils import is_org_context
|
||||
|
||||
from constance import config
|
||||
|
||||
@@ -101,6 +101,8 @@ def logout(request):
|
||||
if hasattr(request, 'user'):
|
||||
from seahub.base.accounts import 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):
|
||||
org_id = request.user.org.org_id
|
||||
request.user.remove_org_repo_passwds(org_id)
|
||||
|
@@ -88,8 +88,7 @@ def edit_profile(request):
|
||||
}
|
||||
|
||||
if has_two_factor_auth():
|
||||
from seahub.two_factor.models import StaticDevice
|
||||
from seahub.two_factor.utils import default_device
|
||||
from seahub.two_factor.models import StaticDevice, default_device
|
||||
|
||||
try:
|
||||
backup_tokens = StaticDevice.objects.get(
|
||||
|
@@ -1,9 +1,4 @@
|
||||
# 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 ##############################
|
||||
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):
|
||||
request.session[DEVICE_ID_SESSION_KEY] = device.persistent_id
|
||||
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.two_factor import user_has_device
|
||||
from seahub.two_factor.models import user_has_device
|
||||
from seahub.two_factor.conf import settings
|
||||
|
||||
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.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.models import (Device, TOTPDevice, StaticDevice,
|
||||
PhoneDevice)
|
||||
|
||||
PhoneDevice, devices_for_user, match_token)
|
||||
|
||||
from .models import get_available_methods
|
||||
from .utils import totp_digits
|
||||
|
@@ -7,7 +7,7 @@ except ImportError:
|
||||
else:
|
||||
User = get_user_model()
|
||||
|
||||
from seahub.two_factor import devices_for_user
|
||||
from seahub.two_factor.models import devices_for_user
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
@@ -2,7 +2,7 @@
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
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()
|
||||
|
||||
|
@@ -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.phone import PhoneDevice
|
||||
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)
|
||||
"""
|
||||
from seahub.two_factor import import_class
|
||||
from seahub.two_factor.utils import import_class
|
||||
|
||||
try:
|
||||
device_type, device_id = path.rsplit('/', 1)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2012-2016 Seafile Ltd.
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
import sys
|
||||
from binascii import hexlify, unhexlify
|
||||
from os import urandom
|
||||
|
||||
@@ -13,17 +14,6 @@ from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
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):
|
||||
# For a complete run-through of all the parameters, have a look at the
|
||||
# specs at:
|
||||
@@ -116,3 +106,18 @@ def random_hex(length=20):
|
||||
:rtype: str
|
||||
"""
|
||||
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.decorators import otp_required
|
||||
from seahub.two_factor.models import (StaticToken, StaticDevice,
|
||||
PhoneDevice, get_available_methods)
|
||||
from seahub.two_factor.utils import random_hex, totp_digits
|
||||
from seahub.two_factor.models import (StaticDevice, PhoneDevice,
|
||||
get_available_methods, default_device)
|
||||
from seahub.two_factor.utils import random_hex, totp_digits, get_otpauth_url
|
||||
|
||||
from seahub.two_factor.forms import (MethodForm, TOTPDeviceForm,
|
||||
PhoneNumberForm, DeviceValidationForm)
|
||||
from seahub.two_factor.utils import get_otpauth_url, default_device
|
||||
from seahub.two_factor.views.utils import (class_view_decorator,
|
||||
CheckTwoFactorEnabledMixin,
|
||||
IdempotentSessionWizardView)
|
||||
|
@@ -28,11 +28,10 @@ from seahub.utils.ip import get_remote_ip
|
||||
from seahub.profile.models import Profile
|
||||
|
||||
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, PhoneDevice
|
||||
from seahub.two_factor.models import (StaticDevice, TOTPDevice, default_device,
|
||||
user_has_device)
|
||||
|
||||
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.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.shortcuts import redirect
|
||||
|
||||
from ..utils import default_device
|
||||
from seahub.two_factor.models import default_device
|
||||
|
||||
|
||||
class OTPRequiredMixin(object):
|
||||
|
@@ -8,10 +8,9 @@ from django.views.generic import TemplateView, FormView
|
||||
|
||||
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.models import StaticDevice
|
||||
from seahub.two_factor.utils import default_device
|
||||
from seahub.two_factor.models import (StaticDevice, devices_for_user,
|
||||
user_has_device, default_device)
|
||||
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, \
|
||||
get_available_admin_roles
|
||||
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, \
|
||||
is_pro_version, send_html_email, get_user_traffic_list, get_server_id, \
|
||||
handle_virus_record, get_virus_record_by_id, \
|
||||
|
@@ -3,6 +3,7 @@ import pytest
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from seahub.test_utils import BaseTestCase
|
||||
from seahub.two_factor.models import TOTPDevice, devices_for_user
|
||||
|
||||
TRAVIS = 'TRAVIS' in os.environ
|
||||
|
||||
@@ -13,12 +14,9 @@ class TwoFactorAuthViewTest(BaseTestCase):
|
||||
self.login_as(self.admin)
|
||||
|
||||
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.save()
|
||||
|
||||
from seahub.two_factor import devices_for_user
|
||||
devices = devices_for_user(self.admin)
|
||||
i = 0
|
||||
for device in devices_for_user(self.admin):
|
||||
|
@@ -1,8 +1,7 @@
|
||||
from django.core.urlresolvers import reverse
|
||||
from constance import config
|
||||
|
||||
from seahub.two_factor.models import StaticDevice
|
||||
from seahub.two_factor import user_has_device
|
||||
from seahub.two_factor.models import StaticDevice, user_has_device
|
||||
from seahub.test_utils import BaseTestCase
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user