Repository: getsentry/sentry

Issue:
MFA configuration should require password confirmation
If you manage to hijack a users session you can prevent the user from logging in by enabling MFA on their account. This means it'd be fairly complex for the user to reclaim the account, as it'd have to go through an administrator. Due to this, we should require password confirmation when enabling or disabling MFA.



Discussion:

Resolving patch:
diff --git a/src/sentry/web/forms/accounts.py b/src/sentry/web/forms/accounts.py
--- a/src/sentry/web/forms/accounts.py
+++ b/src/sentry/web/forms/accounts.py
@@ -531,3 +531,18 @@ class TwoFactorForm(forms.Form):
                    'autofocus': True,
         }),
     )
+    password = forms.CharField(
+        label=_('Sentry account password'),
+        widget=forms.PasswordInput(),
+        help_text='You will need to enter your current Sentry account password to make changes.',
+        required=True,
+    )
+
+
+class ConfirmPasswordForm(forms.Form):
+    password = forms.CharField(
+        label=_('Sentry account password'),
+        widget=forms.PasswordInput(),
+        help_text='You will need to enter your current Sentry account password to make changes.',
+        required=True,
+    )
diff --git a/src/sentry/web/frontend/accounts_twofactor.py b/src/sentry/web/frontend/accounts_twofactor.py
--- a/src/sentry/web/frontend/accounts_twofactor.py
+++ b/src/sentry/web/frontend/accounts_twofactor.py
@@ -17,7 +17,7 @@
 from sentry.web.frontend.base import BaseView
 from sentry.web.decorators import login_required
 from sentry.web.helpers import render_to_response
-from sentry.web.forms.accounts import TwoFactorForm
+from sentry.web.forms.accounts import TwoFactorForm, ConfirmPasswordForm
 from sentry.utils import json
 
 
@@ -78,12 +78,19 @@ def delete_authenticator(self, interface):
                 iface.authenticator.delete()
 
     def remove(self, request, interface):
+        form = ConfirmPasswordForm
+
         if 'no' in request.POST or \
            not interface.is_enrolled:
             return HttpResponseRedirect(reverse('sentry-account-settings-2fa'))
         elif 'yes' in request.POST:
-            self.delete_authenticator(interface)
-            return HttpResponseRedirect(reverse('sentry-account-settings-2fa'))
+            form = ConfirmPasswordForm(request.POST)
+            if form.is_valid():
+                if request.user.check_password(form.cleaned_data['password']):
+                    self.delete_authenticator(interface)
+                    return HttpResponseRedirect(reverse('sentry-account-settings-2fa'))
+                else:
+                    form.errors['__all__'] = ['Invalid password.']
 
         all_interfaces = Authenticator.objects.all_interfaces_for_user(
             request.user)
@@ -94,6 +101,7 @@ def remove(self, request, interface):
             len(backup_interfaces) == len(other_interfaces)
 
         context = self.make_context(request, interface)
+        context['password_form'] = form
         context['removes_backups'] = removes_backups
         return render_to_response('sentry/account/twofactor/remove.html',
                                   context, request)
@@ -151,11 +159,15 @@ def enroll(self, request, interface, insecure=False):
 
         if 'otp' in request.POST:
             form = TwoFactorForm(request.POST)
-            if form.is_valid() and interface.validate_otp(
-                    form.cleaned_data['otp']):
-                return TwoFactorSettingsView.enroll(self, request, interface)
-            else:
-                form.errors['__all__'] = ['Invalid confirmation code.']
+            if form.is_valid():
+                if request.user.check_password(form.cleaned_data['password']):
+                    if form.is_valid() and interface.validate_otp(
+                            form.cleaned_data['otp']):
+                        return TwoFactorSettingsView.enroll(self, request, interface)
+                    else:
+                        form.errors['__all__'] = ['Invalid confirmation code.']
+                else:
+                    form.errors['__all__'] = ['Invalid password.']
         else:
             form = TwoFactorForm()
 


Test patch: