@@ -182,7 +182,13 @@ def done(self, form_list, **kwargs):
182182 samesite = getattr (settings , 'TWO_FACTOR_REMEMBER_COOKIE_SAMESITE' , 'Lax' ),
183183 )
184184
185- return response
185+ return response
186+
187+ # If the user does not have a device.
188+ else :
189+ if self .request .GET .get ('next' ):
190+ self .request .session ['next' ] = self .get_success_url ()
191+ return redirect ('two_factor:setup' )
186192
187193 # Copied from django.conrib.auth.views.LoginView (Branch: stable/1.11.x)
188194 # https://github.com/django/django/blob/58df8aa40fe88f753ba79e091a52f236246260b3/django/contrib/auth/views.py#L63
@@ -394,7 +400,7 @@ def dispatch(self, request, *args, **kwargs):
394400
395401@class_view_decorator (never_cache )
396402@class_view_decorator (login_required )
397- class SetupView (IdempotentSessionWizardView ):
403+ class SetupView (RedirectURLMixin , IdempotentSessionWizardView ):
398404 """
399405 View for handling OTP setup using a wizard.
400406
@@ -417,6 +423,27 @@ class SetupView(IdempotentSessionWizardView):
417423 # Other forms are dynamically added in get_form_list()
418424 )
419425
426+ # Copied from django.conrib.auth.views.LoginView (Branch: stable/1.11.x)
427+ # https://github.com/django/django/blob/58df8aa40fe88f753ba79e091a52f236246260b3/django/contrib/auth/views.py#L63
428+ def get_success_url (self ):
429+ url = self .get_redirect_url ()
430+ return url or reverse ('two_factor:setup_complete' )
431+
432+ # Copied from django.conrib.auth.views.LoginView (Branch: stable/1.11.x)
433+ # https://github.com/django/django/blob/58df8aa40fe88f753ba79e091a52f236246260b3/django/contrib/auth/views.py#L67
434+ def get_redirect_url (self ):
435+ """Return the user-originating redirect URL if it's safe."""
436+ redirect_to = self .request .POST .get (
437+ REDIRECT_FIELD_NAME ,
438+ self .request .GET .get (REDIRECT_FIELD_NAME , '' )
439+ )
440+ url_is_safe = url_has_allowed_host_and_scheme (
441+ url = redirect_to ,
442+ allowed_hosts = self .get_success_url_allowed_hosts (),
443+ require_https = self .request .is_secure (),
444+ )
445+ return redirect_to if url_is_safe else ''
446+
420447 def get_method (self ):
421448 method_data = self .storage .validated_step_data .get ('method' , {})
422449 method_key = method_data .get ('method' , None )
@@ -427,7 +454,7 @@ def get(self, request, *args, **kwargs):
427454 Start the setup wizard. Redirect if already enabled.
428455 """
429456 if default_device (self .request .user ):
430- return redirect (self .success_url )
457+ return redirect (self .get_success_url () )
431458 return super ().get (request , * args , ** kwargs )
432459
433460 def get_form (self , step = None , ** kwargs ):
@@ -495,7 +522,7 @@ def done(self, form_list, **kwargs):
495522 raise NotImplementedError ("Unknown method '%s'" % method .code )
496523
497524 django_otp .login (self .request , device )
498- return redirect (self .success_url )
525+ return redirect (self .get_success_url () )
499526
500527 def get_form_kwargs (self , step = None ):
501528 kwargs = {}
@@ -607,6 +634,11 @@ class SetupCompleteView(TemplateView):
607634 """
608635 template_name = 'two_factor/core/setup_complete.html'
609636
637+ def get (self , request , * args , ** kwargs ):
638+ if request .session .get ('next' ):
639+ return redirect (request .session .get ('next' ))
640+ return super ().get (request , * args , ** kwargs )
641+
610642 def get_context_data (self ):
611643 return {
612644 'phone_methods' : get_available_phone_methods (),
0 commit comments