Example #1
0
    def get(self, request, *args, **kwargs):
        if request.user.is_anonymous():
            raise Http404()

        content_type = 'image/svg+xml; charset=utf-8'

        name = request.GET.get('name', 'default')
        device = request.user.totpdevice_set\
            .filter(confirmed=False, name=name).first()

        if device is None or name == 'default':
            request.user.totpdevice_set.filter(confirmed=False).delete()
            device = TOTPDevice(user=request.user, name=name, confirmed=False)
            device.save()

        secret_key = b32encode(device.bin_key).decode('utf-8')
        issuer = 'getstream.io'

        otpauth_url = 'otpauth://totp/{label}?{query}'.format(
            label=quote('{issuer}: {username}'.format(
                issuer=issuer,
                username=request.user.get_username()
            )),
            query=urlencode((
                ('secret', secret_key),
                ('digits', device.get_digits_display()),
                ('issuer', issuer),
            ))
        )

        img = qrcode.make(otpauth_url, image_factory=SvgPathImage)
        response = HttpResponse(content_type=content_type)
        img.save(response)
        return response
Example #2
0
def add_topt(request, next_page=None):
    next_page, okay_auth, okay_2fa = _setup_view(request, next_page)

    if not okay_auth:
        return ForbiddenResponse(request)

    # This enforces that users have exactly one TOTP. That *seems* like the best practice.
    devices = TOTPDevice.objects.devices_for_user(request.maybe_stale_user, confirmed=True)
    if devices:
        return ForbiddenResponse(request, "You have already configured an authenticator with this account, and cannot add another. Contact %s if you are unable to authenticate with CourSys", (help_email(request),))

    device = TOTPDevice(user=request.maybe_stale_user, name='Authenticator, enabled %s' % (datetime.date.today()))
    device.save()

    l = LogEntry(userid=request.user.username,
                 description=("Added TOPT from %s") % (ip.get_ip(request),),
                 related_object=device)
    l.save()

    # build QR code
    uri = totpauth_url(device)
    qr = qrcode.make(uri, image_factory=qrcode.image.svg.SvgPathImage)
    qrdata = BytesIO()
    qr.save(qrdata)
    # This is the OTP secret (bits) encoded as base32, wrapped in an otpauth URL, encoded as a QR code, encoded as an
    # SVG, encoded as base64, wrapped in a data URL. I'm strangely proud.
    dataurl = (b'data:image/svg+xml;base64,' + base64.b64encode(qrdata.getvalue())).decode("utf-8")

    context = {
        'device': device,
        'dataurl': dataurl,
        'next_page': next_page,
    }
    return render(request, 'otp/add_topt.html', context)
Example #3
0
def add_topt(request, next_page=None):
    next_page, okay_auth, okay_2fa = _setup_view(request, next_page)

    if not okay_auth:
        return ForbiddenResponse(request)

    # This enforces that users have exactly one TOTP. That *seems* like the best practice.
    devices = TOTPDevice.objects.devices_for_user(request.maybe_stale_user, confirmed=True)
    if devices:
        return ForbiddenResponse(request, "You have already configured an authenticator with this account, and cannot add another. Contact %s if you are unable to authenticate with CourSys", (help_email(request),))

    device = TOTPDevice(user=request.maybe_stale_user, name='Authenticator, enabled %s' % (datetime.date.today()))
    device.save()

    l = LogEntry(userid=request.user.username,
                 description=("Added TOPT from %s") % (get_client_ip(request),),
                 related_object=device)
    l.save()

    # build QR code
    uri = totpauth_url(device)
    qr = qrcode.make(uri, image_factory=qrcode.image.svg.SvgPathImage)
    qrdata = BytesIO()
    qr.save(qrdata)
    # This is the OTP secret (bits) encoded as base32, wrapped in an otpauth URL, encoded as a QR code, encoded as an
    # SVG, encoded as base64, wrapped in a data URL. I'm strangely proud.
    dataurl = (b'data:image/svg+xml;base64,' + base64.b64encode(qrdata.getvalue())).decode("utf-8")

    context = {
        'device': device,
        'dataurl': dataurl,
        'next_page': next_page,
    }
    return render(request, 'otp/add_topt.html', context)
Example #4
0
    def post(self, request, *args, **kwargs):
        user = request.user
        tfa_activated = user_has_device(user)
        reqdata = request.POST.copy()
        if tfa_activated:
            form = TwoFactorAuthFormEnabled(prefix='to_enable', data=reqdata)
            if form.is_valid():
                if form.cleaned_data['status']:
                    for device in devices_for_user(user):
                        device.delete()
                return render(
                    request,
                    'registration/2fa-disabled-now.html',
                    {
                        'form': form,
                        'tfa_activated': tfa_activated,
                        'status_changed': form.cleaned_data['status']
                    },
                )
            else:
                return render(
                    request,
                    'registration/2fa.html',
                    {
                        'form': form,
                        'tfa_activated': tfa_activated,
                    },
                )
        else:
            form = TwoFactorAuthFormDisabled(prefix='to_disable', data=reqdata)
            if form.is_valid():
                if form.cleaned_data['status']:
                    device = TOTPDevice(user=user,
                                        name=user.username + '\'s device')
                    device.save()

                from base64 import b32encode
                secret_key = b32encode(device.bin_key)

                return render(
                    request,
                    'registration/2fa-enabled-now.html',
                    {
                        'form': form,
                        'tfa_activated': tfa_activated,
                        'status_changed': form.cleaned_data['status'],
                        'device': device,
                        'secret_key': secret_key,
                    },
                )
            else:
                return render(
                    request,
                    'registration/2fa.html',
                    {
                        'form': form,
                        'tfa_activated': tfa_activated,
                    },
                )
 def create_device_for_tim(self, confirmed=True):
     self.device = TOTPDevice(
         tolerance=30,
         key=self.carte.seed,
         user=self.aidant_tim,
         step=60,
         confirmed=confirmed,
     )
     self.device.save()
Example #6
0
    def new(user):
        p = UserProfile(user=user)
        p.save()
        totpdevice = user.totpdevice_set.filter(confirmed=1)
        if not totpdevice:
            # 创建t-otp device,使user支持t-otp验证
            t = TOTPDevice(name='自动创建', user=user)
            t.save()

        return p
Example #7
0
def otp_config(request):
    """View to manage two-factor (OTP) auth.

    - Show QR code if the device is not confirmed.
    - Allow user to confirm their code, if it not confirmed.  Then
      mark the device as confirmed.
    - If device is already confirmed, then don't show the QR code
      since that would risk it being lost.  However, do show it if the
      user has logged in using OTP (user.is_verified) and you have
      just verified your token.
    """
    if request.user.is_anonymous():
        return HttpResponse("Forbidden: must be logged in", status=403)
    context = c = {}
    # Get devices
    devices = list(django_otp.devices_for_user(request.user,
                                               confirmed=None))  # conf
    # and unconf If there is more than one device, log an error.
    # FIXME: this does not support one-time passwords and multiple
    # devices yet.
    if len(devices) > 1:
        logger.critical("User has more than one OTP device: %s",
                        request.user.id)
    # If the user has no devices, create one now.
    if len(devices) == 0:
        from django_otp.plugins.otp_totp.models import TOTPDevice
        device = TOTPDevice(user=request.user,
                            confirmed=False,
                            name='Auto device')
        device.save()
    # If everything is normal, then select the user's device.
    else:
        device = devices[0]
        c['confirmed'] = device.confirmed

    # Attempt to verify code or confirm the unconfirmed device.
    if request.method == 'POST':
        form = c['otp_form'] = OTPVerifyForm(request.POST)
        form.is_valid()  # validate form - validation is null op.
        success = c['success'] = device.verify_token(
            form.cleaned_data['otp_token'])
        # Success: if the device is unconfirmed, then confirm it.  It
        # can then be used for logging in, etc.
        if success:
            # Do device confirmation
            if not device.confirmed:
                device.confirmed = True
                device.save()
                c['confirmed'] = device.confirmed
            message = c['message'] = 'Code verified.'
        else:
            message = c['message'] = 'Code not successful.'
        # Note that there is no other special action on success.

        # Reset the form - don't display code again or anything.
        form = c['otp_form'] = OTPVerifyForm()
    else:
        form = c['otp_form'] = OTPVerifyForm()

    return TemplateResponse(request, 'koota/otp.html', context)
 def setUpTestData(cls):
     cls.client = Client()
     # Create one responsable
     cls.responsable_tom = AidantFactory(username="******")
     cls.responsable_tom.responsable_de.add(
         cls.responsable_tom.organisation)
     # Create one aidant
     cls.aidant_tim = AidantFactory(
         username="******",
         organisation=cls.responsable_tom.organisation,
         first_name="Tim",
         last_name="Onier",
     )
     # Create one carte TOTP
     cls.carte = CarteTOTPFactory(serial_number="A123",
                                  seed="FA169F10A9",
                                  aidant=cls.aidant_tim)
     cls.org_id = cls.responsable_tom.organisation.id
     # Create one TOTP Device
     cls.device = TOTPDevice(tolerance=30,
                             key=cls.carte.seed,
                             user=cls.aidant_tim,
                             step=60)
     cls.device.save()
     cls.organisation_url = f"/espace-responsable/organisation/{cls.org_id}"
     cls.aidant_url = f"/espace-responsable/aidant/{cls.aidant_tim.id}/"
     cls.validation_url = (
         f"/espace-responsable/aidant/{cls.aidant_tim.id}/valider-carte")
Example #9
0
    def _update_form(self, user):
        super()._update_form(user)

        if "otp_device" in self.fields:
            # filter user OTP devices to keep only TOTP ones
            self.fields["otp_device"].widget.choices = [
                d for d in self.device_choices(user)
                if TOTPDevice.model_label() in d[0]
            ]
Example #10
0
    def __init__(self, user, request=None, *args, **kwargs):
        super(OTPTokenForm, self).__init__(*args, **kwargs)

        self.user = user
        # filter user OTP devices to keep only TOTP ones
        self.fields["otp_device"].choices = [
            d for d in self.device_choices(user)
            if TOTPDevice.model_label() in d[0]
        ]
Example #11
0
def signup(request):
    try:
        if request.method == 'POST':
            form = SignUpForm(request.POST)
            print("ef")
            if form.is_valid():
                print("hI")
                user = form.save()
                user.save()
                totp = TOTPDevice()
                totp.user = user
                totp.name = user.username
                totp.save()
                raw_password = form.cleaned_data.get('password1')
                user = authenticate(username=user.username,
                                    password=raw_password)
                login(request, user)
                return HttpResponseRedirect(reverse('otpsetup'))
            else:
                return redirect(reverse('welcome'))
        else:
            form = SignUpForm()
        return render(request, 'users/register.html', {'form': form})
    except:
        return redirect(reverse('homepage'))
Example #12
0
def login_view(request, *args, **kwargs):

    form = UserLoginForm(request.POST or None)

    if request.POST:
        if form.is_valid():
            print("if")
            user_obj = form.cleaned_data.get('user_obj')
            login(request, user_obj)
            user = request.user
            return redirect('wallet')
        else:
            if 'otp_device' in form.errors:

                user = request.user
                user_gr = MyUsers.objects.get(username=user)
                device = get_user_totp_device(user)
                if not device:
                    device = TOTPDevice(user=user_gr, name=user.username)
                    device.save()
                return HttpResponseRedirect(
                    reverse('qrcode', kwargs={'pk': device.pk}))

    return render(request, "auth/login.html", {"form": form})
Example #13
0
    def test_delete_static_device_for_confirmed_simple_aidants(self):
        aidants = [
            AidantFactory(),
            AidantFactory(is_staff=True),
            AidantFactory(is_staff=True, is_superuser=True),
        ]
        for aidant in aidants:
            add_static_token(aidant.username, 123456)
            TOTPDevice(user=aidant, confirmed=True).save()

        self.assertEqual(StaticToken.objects.count(), 3)
        self.assertEqual(TOTPDevice.objects.count(), 3)

        call_command(self.command_name)

        # only the first aidant's static device was deleted
        self.assertEqual(StaticToken.objects.count(), 2)
Example #14
0
    def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
        user = self.executor.plan.context.get(PLAN_CONTEXT_PENDING_USER)
        if not user:
            LOGGER.debug("No pending user, continuing")
            return self.executor.stage_ok()

        # Currently, this stage only supports one device per user. If the user already
        # has a device, just skip to the next stage
        if TOTPDevice.objects.filter(user=user).exists():
            return self.executor.stage_ok()

        stage: AuthenticatorTOTPStage = self.executor.current_stage

        if SESSION_TOTP_DEVICE not in self.request.session:
            device = TOTPDevice(user=user, confirmed=True, digits=stage.digits)

            self.request.session[SESSION_TOTP_DEVICE] = device
        return super().get(request, *args, **kwargs)
    def get(self, request, *args, **kwargs):
        # Reduce session expiration to 10 minutes during 2FA check (in case user afk).
        request.session.set_expiry(600)

        devices = list(
            (d.persistent_id, d.name) for d in devices_for_user(request.user))
        _next = request.GET.get("next", None)
        if _next:
            request.session["next"] = _next

        # Redirect to available device check page, from most secure to less secure one
        if [d for d in devices if Fido2Device.model_label() in d[0]]:
            return redirect("otp_fido2_check", *args, **kwargs)

        if [d for d in devices if TOTPDevice.model_label() in d[0]]:
            return redirect("otp_totp_check", *args, **kwargs)

        if [d for d in devices if StaticDevice.model_label() in d[0]]:
            return redirect("otp_static_check", *args, **kwargs)
    def setUpTestData(cls):
        cls.client = Client()
        orga = OrganisationFactory()
        # Mario is a responsable without TOTP Device activated
        # => He should see the messages
        cls.responsable_mario = AidantFactory(username="******",
                                              organisation=orga)
        cls.responsable_mario.responsable_de.add(
            cls.responsable_mario.organisation)
        cls.responsable_mario.responsable_de.add(OrganisationFactory())
        # Mickey is a responsable with a TOTP Device activated
        # => He should not see the messages
        cls.responsable_mickey = AidantFactory(username="******",
                                               organisation=orga)
        cls.responsable_mickey.responsable_de.add(
            cls.responsable_mickey.organisation)
        cls.responsable_mickey.responsable_de.add(OrganisationFactory())
        device = TOTPDevice(user=cls.responsable_mickey)
        device.save()
        # Roger is a responsable with an *inactive* TOTP Device
        # (e.g. after an unfinished card activation)
        # => He should see the messages
        cls.responsable_hubert = AidantFactory(username="******",
                                               organisation=orga)
        cls.responsable_hubert.responsable_de.add(
            cls.responsable_hubert.organisation)
        cls.responsable_hubert.responsable_de.add(OrganisationFactory())
        device = TOTPDevice(user=cls.responsable_hubert, confirmed=False)
        device.save()
        # Guy has no TOTP Device but is a simple Aidant
        # => He should not see the messages.
        cls.aidant_guy = AidantFactory(username="******")

        cls.urls_responsables = (
            "/espace-aidant/",
            "/espace-responsable/",
            f"/espace-responsable/organisation/{orga.id}/",
        )
class DissociateCarteTOTPTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.client = Client()
        # Create one responsable : Tom
        cls.responsable_tom = AidantFactory(username="******")
        cls.responsable_tom.responsable_de.add(
            cls.responsable_tom.organisation)
        cls.org_id = cls.responsable_tom.organisation.id
        # Create one aidant : Tim
        cls.aidant_tim = AidantFactory(
            username="******",
            organisation=cls.responsable_tom.organisation,
            first_name="Tim",
            last_name="Onier",
        )
        cls.dissociation_url = (
            f"/espace-responsable/aidant/{cls.aidant_tim.id}/supprimer-carte/")
        cls.aidant_url = f"/espace-responsable/aidant/{cls.aidant_tim.id}/"

    def create_carte_for_tim(self):
        self.carte = CarteTOTPFactory(serial_number="A123",
                                      seed="FA169F10A9",
                                      aidant=self.aidant_tim)

    def create_device_for_tim(self, confirmed=True):
        self.device = TOTPDevice(
            tolerance=30,
            key=self.carte.seed,
            user=self.aidant_tim,
            step=60,
            confirmed=confirmed,
        )
        self.device.save()

    def do_the_checks(self):
        # Submit post and check redirection is correct
        self.client.force_login(self.responsable_tom)
        response = self.client.post(
            self.dissociation_url,
            data={"reason": "perte"},
        )
        self.assertRedirects(response,
                             self.aidant_url,
                             fetch_redirect_response=False)
        response = self.client.get(self.aidant_url)
        response_content = response.content.decode("utf-8")
        self.assertIn("Tout s’est bien passé", response_content)
        # Check card still exists but that TOTP Device doesn't
        carte = CarteTOTP.objects.last()
        self.assertEqual(carte.serial_number, self.carte.serial_number)
        self.assertIsNone(carte.aidant)
        self.assertEqual(TOTPDevice.objects.count(), 0)
        # Check journal entry creation
        journal_entry = Journal.objects.last()
        self.assertEqual(
            journal_entry.action,
            "card_dissociation",
            "A Journal entry should have been created on card validation.",
        )

    def test_dissociation_in_nominal_case(self):
        self.create_carte_for_tim()
        self.create_device_for_tim()
        self.do_the_checks()

    def test_dissociation_without_existing_totp_device(self):
        self.create_carte_for_tim()
        self.do_the_checks()

    def test_dissociation_with_unconfirmed_totp_device(self):
        self.create_carte_for_tim()
        self.create_device_for_tim(confirmed=False)
        self.do_the_checks()
Example #18
0
    def test_otp_auth(self):
        c = Client()
        person = Person.objects.get(userid='ggbaker')
        url = reverse('dashboard:index')

        # no auth: should redirect to 2fa login, and then to password login
        resp = c.get(url)
        self.assertEqual(resp.status_code, 302)
        self.assertTrue(resp['location'].startswith(str(settings.LOGIN_URL) + '?'))

        nexturl = resp['location']
        resp = c.get(nexturl)
        self.assertEqual(resp.status_code, 302)
        self.assertTrue(resp['location'].startswith(str(settings.PASSWORD_LOGIN_URL) + '?'))

        # do the standard Django auth: should be okay for now, since user doesn't need 2FA.
        c.login_user('ggbaker')
        user = User.objects.get(username='******')
        session_info = SessionInfo.for_sessionstore(c.session)

        resp = c.get(url)
        self.assertEqual(resp.status_code, 200)

        # set the user's account to need 2FA: now should be redirected to create a TOPT.
        person.config['2fa'] = True
        person.save()

        resp = c.get(url)
        self.assertEqual(resp.status_code, 302)
        self.assertTrue(resp['location'].startswith(str(settings.LOGIN_URL) + '?'))

        nexturl = resp['location']
        resp = c.get(nexturl)
        self.assertEqual(resp.status_code, 302)
        self.assertTrue(resp['location'].startswith(reverse('otp:add_topt')))

        test_views(self, c, 'otp:', ['add_topt'], {})

        # create a fake TOTP device for the user
        TOTPDevice.objects.filter(user=user).delete()
        key = '0'*20
        device = TOTPDevice(user=user, name='test device', confirmed=True, key=key)
        device.save()

        # ... now we should see the 2FA screen
        resp = c.get(url)
        self.assertEqual(resp.status_code, 302)
        self.assertTrue(resp['location'].startswith(str(settings.LOGIN_URL) + '?'))

        nexturl = resp['location']
        resp = c.get(nexturl)
        self.assertEqual(resp.status_code, 200)

        test_views(self, c, 'otp:', ['login_2fa'], {})

        # if we fake the 2FA login, we should be seeing pages again
        session_info.last_2fa = timezone.now()
        session_info.save()
        # (mock django_otp's login())
        from django_otp import DEVICE_ID_SESSION_KEY
        session = c.session
        session._session[DEVICE_ID_SESSION_KEY] = device.persistent_id
        session.save()

        resp = c.get(url)
        self.assertEqual(resp.status_code, 200)

        # now fiddle with the ages of things and check for the right failures

        # old password login: should redirect -> login -> password login
        session_info.last_auth = timezone.now() - datetime.timedelta(days=8)
        session_info.save()
        resp = c.get(url, follow=True)
        self.assertEqual(len(resp.redirect_chain), 2)
        self.assertTrue(resp.redirect_chain[-1][0].startswith(str(settings.PASSWORD_LOGIN_URL) + '?'))

        # old 2fa: should redirect -> 2fa login
        session_info.last_auth = timezone.now() - datetime.timedelta(hours=1)
        session_info.last_2fa = timezone.now() - datetime.timedelta(days=32)
        session_info.save()

        resp = c.get(url, follow=True)
        self.assertEqual(len(resp.redirect_chain), 1)
        self.assertTrue(resp.redirect_chain[-1][0].startswith(str(settings.LOGIN_URL) + '?'))
Example #19
0
 def create_overtolerant_device(self, aidant):
     tolerant_device = TOTPDevice(user=aidant, tolerance=50, confirmed=True)
     tolerant_device.save()
Example #20
0
 def create_correct_device(self, aidant):
     correct_device = TOTPDevice(user=aidant, tolerance=0, confirmed=True)
     correct_device.save()
Example #21
0
    def save(self, user):
        totp_device = TOTPDevice(name=self.cleaned_data["name"], user=user)
        totp_device.confirmed = False
        totp_device.save()

        return totp_device