Пример #1
0
    def setUp(self):
        self.someuser = User.objects.get(username='******')

        # Activate MFA for someuser
        get_mfa_model().objects.create(
            user=self.someuser,
            secret='dummy_mfa_secret',
            name='app',
            is_primary=True,
            is_active=True,
            _backup_codes='dummy_encoded_codes',
        )
        self.client.login(username='******', password='******')
Пример #2
0
    def setUp(self):
        self.someuser = User.objects.get(username='******')
        self.anotheruser = User.objects.get(username='******')

        # Activate MFA for someuser
        get_mfa_model().objects.create(
            user=self.someuser,
            secret='dummy_mfa_secret',
            name='app',
            is_primary=True,
            is_active=True,
            _backup_codes='dummy_encoded_codes',
        )
        # Ensure `self.client` is not authenticated
        self.client.logout()
Пример #3
0
 class Meta:
     model = get_mfa_model()
     fields = (
         'name',
         'is_primary',
         'is_active',
         'date_created',
         'date_modified',
         'date_disabled',
     )
Пример #4
0
def test_deactivate_an_only_mfa_method(active_user_with_application_otp):
    mfa_method = active_user_with_application_otp.mfa_methods.get(name="app")
    deactivate_mfa_method_command(
        user_id=active_user_with_application_otp.id,
        mfa_method_name=mfa_method.name,
    )
    mfa_model = get_mfa_model()
    mfa_method.refresh_from_db()
    assert mfa_method.is_active is False
    assert len(
        mfa_model.objects.list_active(
            user_id=active_user_with_application_otp.id)) == 0
Пример #5
0
    def test_date_disabled_is_set_when_not_active(self):

        mfa_method = get_mfa_model().objects.create(
            user=self.someuser,
            secret='dummy_mfa_secret',
            name='app',
            is_active=False,
            _backup_codes='dummy_encoded_codes',
        )
        self.assertNotEqual(mfa_method.date_disabled, None)
        mfa_method.date_disabled = None
        mfa_method.save()
        self.assertNotEqual(mfa_method.date_disabled, None)
Пример #6
0
def test_remove_not_encrypted_code(
        active_user_with_non_encrypted_backup_codes):
    user, codes = active_user_with_non_encrypted_backup_codes
    settings = TrenchAPISettings(user_settings={"ENCRYPT_BACKUP_CODES": False},
                                 defaults=DEFAULTS)
    remove_backup_code_command = RemoveBackupCodeCommand(
        mfa_model=get_mfa_model(), settings=settings).execute
    code = next(iter(codes))
    remove_backup_code_command(
        user_id=user.id,
        method_name="email",
        code=code,
    )
Пример #7
0
    def test_date_modified(self):

        mfa_method = get_mfa_model().objects.create(
            user=self.someuser,
            secret='dummy_mfa_secret',
            name='app',
            is_primary=True,
            is_active=True,
            _backup_codes='dummy_encoded_codes',
        )
        date_modified = mfa_method.date_modified
        mfa_method.save()
        self.assertNotEqual(date_modified, mfa_method.date_modified)
        self.assertTrue(date_modified < mfa_method.date_modified)
Пример #8
0
    def test_date_disabled_is_none_when_is_active(self):

        mfa_method = get_mfa_model().objects.create(
            user=self.someuser,
            secret='dummy_mfa_secret',
            name='app',
            is_primary=True,
            is_active=True,
            _backup_codes='dummy_encoded_codes',
        )
        self.assertEqual(mfa_method.date_disabled, None)
        mfa_method.date_disabled = now()
        mfa_method.save()
        self.assertEqual(mfa_method.date_disabled, None)
Пример #9
0
 def post(request: Request) -> Response:
     serializer = MFAMethodCodeSerializer(data=request.data)
     serializer.is_valid(raise_exception=True)
     try:
         method = serializer.validated_data.get("method")
         mfa_model = get_mfa_model()
         if method is None:
             method = mfa_model.objects.get_primary_active_name(
                 user_id=request.user.id)
         mfa = mfa_model.objects.get_by_name(user_id=request.user.id,
                                             name=method)
         return get_mfa_handler(mfa_method=mfa).dispatch_message()
     except MFAValidationError as cause:
         return ErrorResponse(error=cause)
Пример #10
0
    def clean(self):
        cleaned_data = super().clean()
        # `super().clean()` initialize the object `self.user_cache` with
        # the user object retrieved from authentication (if any)
        auth_method = get_mfa_model().objects.filter(
            is_active=True, user=self.user_cache).first()
        # Because we only support one 2FA method, we do not filter on
        # `is_primary` too (as django_trench does).
        # ToDo Figure out why `is_primary` is False sometimes after reactivating
        #  2FA
        if auth_method:
            self.ephemeral_token_cache = (user_token_generator.make_token(
                self.user_cache))

        return cleaned_data
Пример #11
0
    def validate_mfa_not_active(self, user: '******'):
        """
        Raise an exception if MFA is enabled for user's account.
        """

        # This condition is kind of temporary. We can activate/deactivate
        # class based on settings. Useful until we decide whether
        # TokenAuthentication should be deactivated with MFA
        # ToDo Remove the condition when kobotoolbox/kpi#3383 is released/merged
        class_path = f'{self.__module__}.{self.__class__.__name__}'
        if class_path not in settings.MFA_SUPPORTED_AUTH_CLASSES:
            if get_mfa_model().objects.filter(is_active=True, user=user).exists():
                raise exceptions.AuthenticationFailed(gettext(
                    'Multi-factor authentication is enabled for this account. '
                    f'{self.verbose_name} cannot be used.'
                ))
Пример #12
0
 def post(request: Request) -> Response:
     mfa_model = get_mfa_model()
     mfa_method_name = mfa_model.objects.get_primary_active_name(
         user_id=request.user.id)
     serializer = ChangePrimaryMethodValidator(
         user=request.user,
         mfa_method_name=mfa_method_name,
         data=request.data)
     serializer.is_valid(raise_exception=True)
     try:
         set_primary_mfa_method_command(
             user_id=request.user.id,
             name=serializer.validated_data["method"])
         return Response(status=HTTP_204_NO_CONTENT)
     except MFAValidationError as cause:
         return ErrorResponse(error=cause)
Пример #13
0
 def post(self, request: Request) -> Response:
     serializer = LoginSerializer(data=request.data)
     serializer.is_valid(raise_exception=True)
     try:
         user = authenticate_user_command(
             request=request,
             username=serializer.validated_data[User.USERNAME_FIELD],
             password=serializer.validated_data["password"],
         )
     except MFAValidationError as cause:
         return ErrorResponse(error=cause)
     try:
         mfa_model = get_mfa_model()
         mfa_method = mfa_model.objects.get_primary_active(user_id=user.id)
         get_mfa_handler(mfa_method=mfa_method).dispatch_message()
         return Response(
             data={
                 "ephemeral_token": user_token_generator.make_token(user),
                 "method": mfa_method.name,
             })
     except MFAMethodDoesNotExistError:
         return self._successful_authentication_response(user=user)
Пример #14
0
    def validate_code(self, value: str) -> str:
        if not value:
            raise OTPCodeMissingError()
        mfa_model = get_mfa_model()
        mfa = mfa_model.objects.get_by_name(user_id=self._user.id,
                                            name=self._mfa_method_name)
        self._validate_mfa_method(mfa)

        validated_backup_code = validate_backup_code_command(
            value=value, backup_codes=mfa.backup_codes)

        handler = get_mfa_handler(mfa)
        validation_method = getattr(handler,
                                    self._get_validation_method_name())
        if validation_method(value):
            return value

        if validated_backup_code:
            remove_backup_code_command(user_id=mfa.user_id,
                                       method_name=mfa.name,
                                       code=value)
            return value

        raise CodeInvalidOrExpiredError()
Пример #15
0
from django.db.transaction import atomic

from typing import Type

from trench.exceptions import MFAMethodDoesNotExistError, MFAPrimaryMethodInactiveError
from trench.models import MFAMethod
from trench.utils import get_mfa_model


class SetPrimaryMFAMethodCommand:
    def __init__(self, mfa_model: Type[MFAMethod]) -> None:
        self._mfa_model = mfa_model

    @atomic
    def execute(self, user_id: int, name: str) -> None:
        self._mfa_model.objects.filter(user_id=user_id, is_primary=True).update(
            is_primary=False
        )
        if not self._mfa_model.objects.is_active_by_name(user_id=user_id, name=name):
            raise MFAPrimaryMethodInactiveError()
        rows_affected = self._mfa_model.objects.filter(
            user_id=user_id, name=name
        ).update(is_primary=True)
        if rows_affected < 1:
            raise MFAMethodDoesNotExistError()


set_primary_mfa_method_command = SetPrimaryMFAMethodCommand(
    mfa_model=get_mfa_model()
).execute
Пример #16
0
from trench.settings import api_settings
from trench.utils import (
    create_secret,
    get_mfa_handler,
    get_mfa_model,
    get_nested_attr,
    set_nested_attr,
    user_token_generator,
    validate_backup_code,
    validate_code,
)


User = get_user_model()
MFAMethod = get_mfa_model()

mfa_methods_items = api_settings.MFA_METHODS.items()
MFA_METHODS = [
    (k, v.get('VERBOSE_NAME', _(k))) for k, v in mfa_methods_items
]


class RequestMFAMethodActivationSerializer(serializers.Serializer):
    serializer_field_mapping = {
        django_models.AutoField: fields.IntegerField,
        django_models.BigIntegerField: fields.IntegerField,
        django_models.BooleanField: fields.BooleanField,
        django_models.CharField: fields.CharField,
        django_models.CommaSeparatedIntegerField: fields.CharField,
        django_models.DateField: fields.DateField,
Пример #17
0
from django.db.transaction import atomic

from typing import Type

from trench.exceptions import DeactivationOfPrimaryMFAMethodError, MFANotEnabledError
from trench.models import MFAMethod
from trench.utils import get_mfa_model


class DeactivateMFAMethodCommand:
    def __init__(self, mfa_model: Type[MFAMethod]) -> None:
        self._mfa_model = mfa_model

    @atomic
    def execute(self, mfa_method_name: str, user_id: int) -> None:
        mfa = self._mfa_model.objects.get_by_name(user_id=user_id,
                                                  name=mfa_method_name)
        number_of_active_mfa_methods = self._mfa_model.objects.filter(
            user_id=user_id, is_active=True).count()
        if mfa.is_primary and number_of_active_mfa_methods > 1:
            raise DeactivationOfPrimaryMFAMethodError()
        if not mfa.is_active:
            raise MFANotEnabledError()

        self._mfa_model.objects.filter(
            user_id=user_id, name=mfa_method_name).update(is_active=False)


deactivate_mfa_method_command = DeactivateMFAMethodCommand(
    mfa_model=get_mfa_model()).execute
Пример #18
0
 class Meta:
     model = get_mfa_model()
     fields = ("name", "is_primary")
Пример #19
0
from typing import Callable, Type

from trench.command.create_secret import create_secret_command
from trench.exceptions import MFAMethodAlreadyActiveError
from trench.models import MFAMethod
from trench.utils import get_mfa_model


class CreateMFAMethodCommand:
    def __init__(self, secret_generator: Callable, mfa_model: Type[MFAMethod]) -> None:
        self._mfa_model = mfa_model
        self._create_secret = secret_generator

    def execute(self, user_id: int, name: str) -> MFAMethod:
        mfa, created = self._mfa_model.objects.get_or_create(
            user_id=user_id,
            name=name,
            defaults={
                "secret": self._create_secret,
                "is_active": False,
            },
        )
        if not created and mfa.is_active:
            raise MFAMethodAlreadyActiveError()
        return mfa


create_mfa_method_command = CreateMFAMethodCommand(
    secret_generator=create_secret_command, mfa_model=get_mfa_model()
).execute
Пример #20
0
 def get_queryset(self) -> QuerySet:
     mfa_model = get_mfa_model()
     return mfa_model.objects.list_active(user_id=self.request.user.id)