We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Password Reset Hidden while waiting for 2FA
https://github.com/ocadotechnology/rapid-router/issues/684

Original message: 
Steps to reproduce on a 2FA enabled account:
1. Got to /teach/, enter login details and click "Sign in".
2. When the token form appears, go back to teach and click "Forgotten Password?" link

You'll end up back at teach and the url will change to "/teach/?next=/teach/home/"

It seems that if 2FA login process is pending, the password reset screen gets redirected back to /teach/.

---

=> This is because after the sign in attempt, the teacher is no longer "not_logged_in" because a UserProfile has been found.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `portal/permissions.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 # Code for Life
3 #
4 # Copyright (C) 2015, Ocado Innovation Limited
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Affero General Public License as
8 # published by the Free Software Foundation, either version 3 of the
9 # License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU Affero General Public License for more details.
15 #
16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19 # ADDITIONAL TERMS – Section 7 GNU General Public Licence
20 #
21 # This licence does not grant any right, title or interest in any “Ocado” logos,
22 # trade names or the trademark “Ocado” or any other trademarks or domain names
23 # owned by Ocado Innovation Limited or the Ocado group of companies or any other
24 # distinctive brand features of “Ocado” as may be secured from time to time. You
25 # must not distribute any modification of this program using the trademark
26 # “Ocado” or claim any affiliation or association with Ocado or its employees.
27 #
28 # You are not authorised to use the name Ocado (or any of its trade names) or
29 # the names of any author or contributor in advertising or for publicity purposes
30 # pertaining to the distribution of this program, without the prior written
31 # authorisation of Ocado.
32 #
33 # Any propagation, distribution or conveyance of this program must include this
34 # copyright notice and these terms. You must not misrepresent the origins of this
35 # program; modified versions of the program must be marked as such and not
36 # identified as the original program.
37 from functools import wraps
38 from django.http import HttpResponseRedirect
39 from django.core.urlresolvers import reverse_lazy
40 
41 from portal.utils import using_two_factor
42 
43 
44 def logged_in_as_teacher(u):
45     if not hasattr(u, 'userprofile') or not hasattr(u.userprofile, 'teacher'):
46         return False
47 
48     return u.is_verified() or not using_two_factor(u)
49 
50 
51 def logged_in_as_student(u):
52     return hasattr(u, 'userprofile') and hasattr(u.userprofile, 'student')
53 
54 
55 def not_logged_in(u):
56     return not hasattr(u, 'userprofile')
57 
58 
59 def teacher_verified(view_func):
60     @wraps(view_func)
61     def wrapped(request, *args, **kwargs):
62         u = request.user
63         if (not hasattr(u, 'userprofile') or not hasattr(u.userprofile, 'teacher') or
64                 (not u.is_verified() and using_two_factor(u))):
65             return HttpResponseRedirect(reverse_lazy('teach'))
66 
67         return view_func(request, *args, **kwargs)
68 
69     return wrapped
70
```

Path: `portal/views/registration.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 # Code for Life
3 #
4 # Copyright (C) 2015, Ocado Innovation Limited
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Affero General Public License as
8 # published by the Free Software Foundation, either version 3 of the
9 # License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU Affero General Public License for more details.
15 #
16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19 # ADDITIONAL TERMS – Section 7 GNU General Public Licence
20 #
21 # This licence does not grant any right, title or interest in any “Ocado” logos,
22 # trade names or the trademark “Ocado” or any other trademarks or domain names
23 # owned by Ocado Innovation Limited or the Ocado group of companies or any other
24 # distinctive brand features of “Ocado” as may be secured from time to time. You
25 # must not distribute any modification of this program using the trademark
26 # “Ocado” or claim any affiliation or association with Ocado or its employees.
27 #
28 # You are not authorised to use the name Ocado (or any of its trade names) or
29 # the names of any author or contributor in advertising or for publicity purposes
30 # pertaining to the distribution of this program, without the prior written
31 # authorisation of Ocado.
32 #
33 # Any propagation, distribution or conveyance of this program must include this
34 # copyright notice and these terms. You must not misrepresent the origins of this
35 # program; modified versions of the program must be marked as such and not
36 # identified as the original program.
37 
38 from django.http import HttpResponseRedirect
39 from django.utils.http import urlsafe_base64_decode
40 from django.core.urlresolvers import reverse_lazy
41 from django.contrib.auth.decorators import user_passes_test
42 from django.contrib.auth.views import password_reset, password_reset_confirm
43 from django.contrib.auth import get_user_model
44 from two_factor.views import LoginView
45 from recaptcha import RecaptchaClient
46 from django_recaptcha_field import create_form_subclass_with_recaptcha
47 from deploy.captcha import CAPTCHA_ENABLED
48 
49 from portal.forms.registration import PasswordResetSetPasswordForm, StudentPasswordResetForm, TeacherPasswordResetForm
50 from portal.permissions import not_logged_in
51 from portal.helpers.email import PASSWORD_RESET_EMAIL
52 from portal import app_settings
53 from ratelimit.decorators import ratelimit
54 
55 recaptcha_client = RecaptchaClient(app_settings.RECAPTCHA_PRIVATE_KEY, app_settings.RECAPTCHA_PUBLIC_KEY)
56 
57 @ratelimit('def', periods=['1m'])
58 def custom_2FA_login(request):
59     block_limit = 5
60 
61     if getattr(request, 'limits', { 'def' : [0] })['def'][0] >= block_limit:
62         return HttpResponseRedirect(reverse_lazy('locked_out'))
63 
64     return LoginView.as_view()(request)
65 
66 
67 @user_passes_test(not_logged_in, login_url=reverse_lazy('current_user'))
68 def password_reset_check_and_confirm(request, uidb64=None, token=None, post_reset_redirect=None):
69     # Customised standard django auth view with customised form to incorporate checking the password set is strong enough
70     UserModel = get_user_model()
71     try:
72         uid = urlsafe_base64_decode(uidb64)
73         user = UserModel._default_manager.get(pk=uid)
74     except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):
75         user = None
76     if user and hasattr(user.userprofile, 'student'):
77         usertype = 'STUDENT'
78     else:
79         usertype = 'TEACHER'
80     return password_reset_confirm(request, set_password_form=PasswordResetSetPasswordForm, uidb64=uidb64, token=token, post_reset_redirect=post_reset_redirect, extra_context= { 'usertype': usertype })
81 
82 
83 @user_passes_test(not_logged_in, login_url=reverse_lazy('current_user'))
84 def student_password_reset(request, post_reset_redirect):
85     form = StudentPasswordResetForm if not CAPTCHA_ENABLED else decorate_with_captcha(StudentPasswordResetForm, request,
86                                                                                    recaptcha_client)
87     return password_reset(request, from_email=PASSWORD_RESET_EMAIL, template_name='registration/student_password_reset_form.html', password_reset_form=form, post_reset_redirect=post_reset_redirect, is_admin_site=True)
88 
89 
90 @user_passes_test(not_logged_in, login_url=reverse_lazy('current_user'))
91 def teacher_password_reset(request, post_reset_redirect):
92     form = TeacherPasswordResetForm if not CAPTCHA_ENABLED else decorate_with_captcha(TeacherPasswordResetForm, request,
93                                                                                    recaptcha_client)
94     return password_reset(request, from_email=PASSWORD_RESET_EMAIL, template_name='registration/teacher_password_reset_form.html', password_reset_form=form, post_reset_redirect=post_reset_redirect, is_admin_site=True)
95 
96 
97 def decorate_with_captcha(base_class, request, recaptcha_client):
98     form_with_captcha_class = create_form_subclass_with_recaptcha(base_class, recaptcha_client)
99 
100     class FormWithCaptcha(form_with_captcha_class):
101 
102         def __init__(self, *args, **kwargs):
103             super(FormWithCaptcha, self).__init__(request, *args, **kwargs)
104 
105     return FormWithCaptcha
106
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/portal/permissions.py b/portal/permissions.py
--- a/portal/permissions.py
+++ b/portal/permissions.py
@@ -56,6 +56,10 @@
     return not hasattr(u, 'userprofile')
 
 
+def not_fully_logged_in(u):
+    return not_logged_in(u) or (not logged_in_as_student(u) and not logged_in_as_teacher(u))
+
+
 def teacher_verified(view_func):
     @wraps(view_func)
     def wrapped(request, *args, **kwargs):
diff --git a/portal/views/registration.py b/portal/views/registration.py
--- a/portal/views/registration.py
+++ b/portal/views/registration.py
@@ -47,7 +47,7 @@
 from deploy.captcha import CAPTCHA_ENABLED
 
 from portal.forms.registration import PasswordResetSetPasswordForm, StudentPasswordResetForm, TeacherPasswordResetForm
-from portal.permissions import not_logged_in
+from portal.permissions import not_logged_in, not_fully_logged_in
 from portal.helpers.email import PASSWORD_RESET_EMAIL
 from portal import app_settings
 from ratelimit.decorators import ratelimit
@@ -64,7 +64,7 @@
     return LoginView.as_view()(request)
 
 
-@user_passes_test(not_logged_in, login_url=reverse_lazy('current_user'))
+@user_passes_test(not_fully_logged_in, login_url=reverse_lazy('current_user'))
 def password_reset_check_and_confirm(request, uidb64=None, token=None, post_reset_redirect=None):
     # Customised standard django auth view with customised form to incorporate checking the password set is strong enough
     UserModel = get_user_model()
@@ -87,7 +87,7 @@
     return password_reset(request, from_email=PASSWORD_RESET_EMAIL, template_name='registration/student_password_reset_form.html', password_reset_form=form, post_reset_redirect=post_reset_redirect, is_admin_site=True)
 
 
-@user_passes_test(not_logged_in, login_url=reverse_lazy('current_user'))
+@user_passes_test(not_fully_logged_in, login_url=reverse_lazy('current_user'))
 def teacher_password_reset(request, post_reset_redirect):
     form = TeacherPasswordResetForm if not CAPTCHA_ENABLED else decorate_with_captcha(TeacherPasswordResetForm, request,
                                                                                    recaptcha_client)

{"golden_diff": "diff --git a/portal/permissions.py b/portal/permissions.py\n--- a/portal/permissions.py\n+++ b/portal/permissions.py\n@@ -56,6 +56,10 @@\n     return not hasattr(u, 'userprofile')\n \n \n+def not_fully_logged_in(u):\n+    return not_logged_in(u) or (not logged_in_as_student(u) and not logged_in_as_teacher(u))\n+\n+\n def teacher_verified(view_func):\n     @wraps(view_func)\n     def wrapped(request, *args, **kwargs):\ndiff --git a/portal/views/registration.py b/portal/views/registration.py\n--- a/portal/views/registration.py\n+++ b/portal/views/registration.py\n@@ -47,7 +47,7 @@\n from deploy.captcha import CAPTCHA_ENABLED\n \n from portal.forms.registration import PasswordResetSetPasswordForm, StudentPasswordResetForm, TeacherPasswordResetForm\n-from portal.permissions import not_logged_in\n+from portal.permissions import not_logged_in, not_fully_logged_in\n from portal.helpers.email import PASSWORD_RESET_EMAIL\n from portal import app_settings\n from ratelimit.decorators import ratelimit\n@@ -64,7 +64,7 @@\n     return LoginView.as_view()(request)\n \n \n-@user_passes_test(not_logged_in, login_url=reverse_lazy('current_user'))\n+@user_passes_test(not_fully_logged_in, login_url=reverse_lazy('current_user'))\n def password_reset_check_and_confirm(request, uidb64=None, token=None, post_reset_redirect=None):\n     # Customised standard django auth view with customised form to incorporate checking the password set is strong enough\n     UserModel = get_user_model()\n@@ -87,7 +87,7 @@\n     return password_reset(request, from_email=PASSWORD_RESET_EMAIL, template_name='registration/student_password_reset_form.html', password_reset_form=form, post_reset_redirect=post_reset_redirect, is_admin_site=True)\n \n \n-@user_passes_test(not_logged_in, login_url=reverse_lazy('current_user'))\n+@user_passes_test(not_fully_logged_in, login_url=reverse_lazy('current_user'))\n def teacher_password_reset(request, post_reset_redirect):\n     form = TeacherPasswordResetForm if not CAPTCHA_ENABLED else decorate_with_captcha(TeacherPasswordResetForm, request,\n                                                                                    recaptcha_client)\n", "issue": "Password Reset Hidden while waiting for 2FA\nhttps://github.com/ocadotechnology/rapid-router/issues/684\n\nOriginal message: \nSteps to reproduce on a 2FA enabled account:\n1. Got to /teach/, enter login details and click \"Sign in\".\n2. When the token form appears, go back to teach and click \"Forgotten Password?\" link\n\nYou'll end up back at teach and the url will change to \"/teach/?next=/teach/home/\"\n\nIt seems that if 2FA login process is pending, the password reset screen gets redirected back to /teach/.\n\n---\n\n=> This is because after the sign in attempt, the teacher is no longer \"not_logged_in\" because a UserProfile has been found.\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n# Code for Life\n#\n# Copyright (C) 2015, Ocado Innovation Limited\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as\n# published by the Free Software Foundation, either version 3 of the\n# License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# ADDITIONAL TERMS \u2013 Section 7 GNU General Public Licence\n#\n# This licence does not grant any right, title or interest in any \u201cOcado\u201d logos,\n# trade names or the trademark \u201cOcado\u201d or any other trademarks or domain names\n# owned by Ocado Innovation Limited or the Ocado group of companies or any other\n# distinctive brand features of \u201cOcado\u201d as may be secured from time to time. You\n# must not distribute any modification of this program using the trademark\n# \u201cOcado\u201d or claim any affiliation or association with Ocado or its employees.\n#\n# You are not authorised to use the name Ocado (or any of its trade names) or\n# the names of any author or contributor in advertising or for publicity purposes\n# pertaining to the distribution of this program, without the prior written\n# authorisation of Ocado.\n#\n# Any propagation, distribution or conveyance of this program must include this\n# copyright notice and these terms. You must not misrepresent the origins of this\n# program; modified versions of the program must be marked as such and not\n# identified as the original program.\nfrom functools import wraps\nfrom django.http import HttpResponseRedirect\nfrom django.core.urlresolvers import reverse_lazy\n\nfrom portal.utils import using_two_factor\n\n\ndef logged_in_as_teacher(u):\n    if not hasattr(u, 'userprofile') or not hasattr(u.userprofile, 'teacher'):\n        return False\n\n    return u.is_verified() or not using_two_factor(u)\n\n\ndef logged_in_as_student(u):\n    return hasattr(u, 'userprofile') and hasattr(u.userprofile, 'student')\n\n\ndef not_logged_in(u):\n    return not hasattr(u, 'userprofile')\n\n\ndef teacher_verified(view_func):\n    @wraps(view_func)\n    def wrapped(request, *args, **kwargs):\n        u = request.user\n        if (not hasattr(u, 'userprofile') or not hasattr(u.userprofile, 'teacher') or\n                (not u.is_verified() and using_two_factor(u))):\n            return HttpResponseRedirect(reverse_lazy('teach'))\n\n        return view_func(request, *args, **kwargs)\n\n    return wrapped\n", "path": "portal/permissions.py"}, {"content": "# -*- coding: utf-8 -*-\n# Code for Life\n#\n# Copyright (C) 2015, Ocado Innovation Limited\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as\n# published by the Free Software Foundation, either version 3 of the\n# License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# ADDITIONAL TERMS \u2013 Section 7 GNU General Public Licence\n#\n# This licence does not grant any right, title or interest in any \u201cOcado\u201d logos,\n# trade names or the trademark \u201cOcado\u201d or any other trademarks or domain names\n# owned by Ocado Innovation Limited or the Ocado group of companies or any other\n# distinctive brand features of \u201cOcado\u201d as may be secured from time to time. You\n# must not distribute any modification of this program using the trademark\n# \u201cOcado\u201d or claim any affiliation or association with Ocado or its employees.\n#\n# You are not authorised to use the name Ocado (or any of its trade names) or\n# the names of any author or contributor in advertising or for publicity purposes\n# pertaining to the distribution of this program, without the prior written\n# authorisation of Ocado.\n#\n# Any propagation, distribution or conveyance of this program must include this\n# copyright notice and these terms. You must not misrepresent the origins of this\n# program; modified versions of the program must be marked as such and not\n# identified as the original program.\n\nfrom django.http import HttpResponseRedirect\nfrom django.utils.http import urlsafe_base64_decode\nfrom django.core.urlresolvers import reverse_lazy\nfrom django.contrib.auth.decorators import user_passes_test\nfrom django.contrib.auth.views import password_reset, password_reset_confirm\nfrom django.contrib.auth import get_user_model\nfrom two_factor.views import LoginView\nfrom recaptcha import RecaptchaClient\nfrom django_recaptcha_field import create_form_subclass_with_recaptcha\nfrom deploy.captcha import CAPTCHA_ENABLED\n\nfrom portal.forms.registration import PasswordResetSetPasswordForm, StudentPasswordResetForm, TeacherPasswordResetForm\nfrom portal.permissions import not_logged_in\nfrom portal.helpers.email import PASSWORD_RESET_EMAIL\nfrom portal import app_settings\nfrom ratelimit.decorators import ratelimit\n\nrecaptcha_client = RecaptchaClient(app_settings.RECAPTCHA_PRIVATE_KEY, app_settings.RECAPTCHA_PUBLIC_KEY)\n\n@ratelimit('def', periods=['1m'])\ndef custom_2FA_login(request):\n    block_limit = 5\n\n    if getattr(request, 'limits', { 'def' : [0] })['def'][0] >= block_limit:\n        return HttpResponseRedirect(reverse_lazy('locked_out'))\n\n    return LoginView.as_view()(request)\n\n\n@user_passes_test(not_logged_in, login_url=reverse_lazy('current_user'))\ndef password_reset_check_and_confirm(request, uidb64=None, token=None, post_reset_redirect=None):\n    # Customised standard django auth view with customised form to incorporate checking the password set is strong enough\n    UserModel = get_user_model()\n    try:\n        uid = urlsafe_base64_decode(uidb64)\n        user = UserModel._default_manager.get(pk=uid)\n    except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):\n        user = None\n    if user and hasattr(user.userprofile, 'student'):\n        usertype = 'STUDENT'\n    else:\n        usertype = 'TEACHER'\n    return password_reset_confirm(request, set_password_form=PasswordResetSetPasswordForm, uidb64=uidb64, token=token, post_reset_redirect=post_reset_redirect, extra_context= { 'usertype': usertype })\n\n\n@user_passes_test(not_logged_in, login_url=reverse_lazy('current_user'))\ndef student_password_reset(request, post_reset_redirect):\n    form = StudentPasswordResetForm if not CAPTCHA_ENABLED else decorate_with_captcha(StudentPasswordResetForm, request,\n                                                                                   recaptcha_client)\n    return password_reset(request, from_email=PASSWORD_RESET_EMAIL, template_name='registration/student_password_reset_form.html', password_reset_form=form, post_reset_redirect=post_reset_redirect, is_admin_site=True)\n\n\n@user_passes_test(not_logged_in, login_url=reverse_lazy('current_user'))\ndef teacher_password_reset(request, post_reset_redirect):\n    form = TeacherPasswordResetForm if not CAPTCHA_ENABLED else decorate_with_captcha(TeacherPasswordResetForm, request,\n                                                                                   recaptcha_client)\n    return password_reset(request, from_email=PASSWORD_RESET_EMAIL, template_name='registration/teacher_password_reset_form.html', password_reset_form=form, post_reset_redirect=post_reset_redirect, is_admin_site=True)\n\n\ndef decorate_with_captcha(base_class, request, recaptcha_client):\n    form_with_captcha_class = create_form_subclass_with_recaptcha(base_class, recaptcha_client)\n\n    class FormWithCaptcha(form_with_captcha_class):\n\n        def __init__(self, *args, **kwargs):\n            super(FormWithCaptcha, self).__init__(request, *args, **kwargs)\n\n    return FormWithCaptcha\n", "path": "portal/views/registration.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n# Code for Life\n#\n# Copyright (C) 2015, Ocado Innovation Limited\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as\n# published by the Free Software Foundation, either version 3 of the\n# License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# ADDITIONAL TERMS \u2013 Section 7 GNU General Public Licence\n#\n# This licence does not grant any right, title or interest in any \u201cOcado\u201d logos,\n# trade names or the trademark \u201cOcado\u201d or any other trademarks or domain names\n# owned by Ocado Innovation Limited or the Ocado group of companies or any other\n# distinctive brand features of \u201cOcado\u201d as may be secured from time to time. You\n# must not distribute any modification of this program using the trademark\n# \u201cOcado\u201d or claim any affiliation or association with Ocado or its employees.\n#\n# You are not authorised to use the name Ocado (or any of its trade names) or\n# the names of any author or contributor in advertising or for publicity purposes\n# pertaining to the distribution of this program, without the prior written\n# authorisation of Ocado.\n#\n# Any propagation, distribution or conveyance of this program must include this\n# copyright notice and these terms. You must not misrepresent the origins of this\n# program; modified versions of the program must be marked as such and not\n# identified as the original program.\nfrom functools import wraps\nfrom django.http import HttpResponseRedirect\nfrom django.core.urlresolvers import reverse_lazy\n\nfrom portal.utils import using_two_factor\n\n\ndef logged_in_as_teacher(u):\n    if not hasattr(u, 'userprofile') or not hasattr(u.userprofile, 'teacher'):\n        return False\n\n    return u.is_verified() or not using_two_factor(u)\n\n\ndef logged_in_as_student(u):\n    return hasattr(u, 'userprofile') and hasattr(u.userprofile, 'student')\n\n\ndef not_logged_in(u):\n    return not hasattr(u, 'userprofile')\n\n\ndef not_fully_logged_in(u):\n    return not_logged_in(u) or (not logged_in_as_student(u) and not logged_in_as_teacher(u))\n\n\ndef teacher_verified(view_func):\n    @wraps(view_func)\n    def wrapped(request, *args, **kwargs):\n        u = request.user\n        if (not hasattr(u, 'userprofile') or not hasattr(u.userprofile, 'teacher') or\n                (not u.is_verified() and using_two_factor(u))):\n            return HttpResponseRedirect(reverse_lazy('teach'))\n\n        return view_func(request, *args, **kwargs)\n\n    return wrapped\n", "path": "portal/permissions.py"}, {"content": "# -*- coding: utf-8 -*-\n# Code for Life\n#\n# Copyright (C) 2015, Ocado Innovation Limited\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU Affero General Public License as\n# published by the Free Software Foundation, either version 3 of the\n# License, or (at your option) any later version.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU Affero General Public License for more details.\n#\n# You should have received a copy of the GNU Affero General Public License\n# along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# ADDITIONAL TERMS \u2013 Section 7 GNU General Public Licence\n#\n# This licence does not grant any right, title or interest in any \u201cOcado\u201d logos,\n# trade names or the trademark \u201cOcado\u201d or any other trademarks or domain names\n# owned by Ocado Innovation Limited or the Ocado group of companies or any other\n# distinctive brand features of \u201cOcado\u201d as may be secured from time to time. You\n# must not distribute any modification of this program using the trademark\n# \u201cOcado\u201d or claim any affiliation or association with Ocado or its employees.\n#\n# You are not authorised to use the name Ocado (or any of its trade names) or\n# the names of any author or contributor in advertising or for publicity purposes\n# pertaining to the distribution of this program, without the prior written\n# authorisation of Ocado.\n#\n# Any propagation, distribution or conveyance of this program must include this\n# copyright notice and these terms. You must not misrepresent the origins of this\n# program; modified versions of the program must be marked as such and not\n# identified as the original program.\n\nfrom django.http import HttpResponseRedirect\nfrom django.utils.http import urlsafe_base64_decode\nfrom django.core.urlresolvers import reverse_lazy\nfrom django.contrib.auth.decorators import user_passes_test\nfrom django.contrib.auth.views import password_reset, password_reset_confirm\nfrom django.contrib.auth import get_user_model\nfrom two_factor.views import LoginView\nfrom recaptcha import RecaptchaClient\nfrom django_recaptcha_field import create_form_subclass_with_recaptcha\nfrom deploy.captcha import CAPTCHA_ENABLED\n\nfrom portal.forms.registration import PasswordResetSetPasswordForm, StudentPasswordResetForm, TeacherPasswordResetForm\nfrom portal.permissions import not_logged_in, not_fully_logged_in\nfrom portal.helpers.email import PASSWORD_RESET_EMAIL\nfrom portal import app_settings\nfrom ratelimit.decorators import ratelimit\n\nrecaptcha_client = RecaptchaClient(app_settings.RECAPTCHA_PRIVATE_KEY, app_settings.RECAPTCHA_PUBLIC_KEY)\n\n@ratelimit('def', periods=['1m'])\ndef custom_2FA_login(request):\n    block_limit = 5\n\n    if getattr(request, 'limits', { 'def' : [0] })['def'][0] >= block_limit:\n        return HttpResponseRedirect(reverse_lazy('locked_out'))\n\n    return LoginView.as_view()(request)\n\n\n@user_passes_test(not_fully_logged_in, login_url=reverse_lazy('current_user'))\ndef password_reset_check_and_confirm(request, uidb64=None, token=None, post_reset_redirect=None):\n    # Customised standard django auth view with customised form to incorporate checking the password set is strong enough\n    UserModel = get_user_model()\n    try:\n        uid = urlsafe_base64_decode(uidb64)\n        user = UserModel._default_manager.get(pk=uid)\n    except (TypeError, ValueError, OverflowError, UserModel.DoesNotExist):\n        user = None\n    if user and hasattr(user.userprofile, 'student'):\n        usertype = 'STUDENT'\n    else:\n        usertype = 'TEACHER'\n    return password_reset_confirm(request, set_password_form=PasswordResetSetPasswordForm, uidb64=uidb64, token=token, post_reset_redirect=post_reset_redirect, extra_context= { 'usertype': usertype })\n\n\n@user_passes_test(not_logged_in, login_url=reverse_lazy('current_user'))\ndef student_password_reset(request, post_reset_redirect):\n    form = StudentPasswordResetForm if not CAPTCHA_ENABLED else decorate_with_captcha(StudentPasswordResetForm, request,\n                                                                                   recaptcha_client)\n    return password_reset(request, from_email=PASSWORD_RESET_EMAIL, template_name='registration/student_password_reset_form.html', password_reset_form=form, post_reset_redirect=post_reset_redirect, is_admin_site=True)\n\n\n@user_passes_test(not_fully_logged_in, login_url=reverse_lazy('current_user'))\ndef teacher_password_reset(request, post_reset_redirect):\n    form = TeacherPasswordResetForm if not CAPTCHA_ENABLED else decorate_with_captcha(TeacherPasswordResetForm, request,\n                                                                                   recaptcha_client)\n    return password_reset(request, from_email=PASSWORD_RESET_EMAIL, template_name='registration/teacher_password_reset_form.html', password_reset_form=form, post_reset_redirect=post_reset_redirect, is_admin_site=True)\n\n\ndef decorate_with_captcha(base_class, request, recaptcha_client):\n    form_with_captcha_class = create_form_subclass_with_recaptcha(base_class, recaptcha_client)\n\n    class FormWithCaptcha(form_with_captcha_class):\n\n        def __init__(self, *args, **kwargs):\n            super(FormWithCaptcha, self).__init__(request, *args, **kwargs)\n\n    return FormWithCaptcha\n", "path": "portal/views/registration.py"}]}