We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Prevent Referer leaking on password reset page
Right now when you get a password reset link, the page you end up on contains the reset token in the url.

Doing this causes a problem in which any page redirected to after this, and _especially_ any third party assets being loaded leak this reset token via the `Referer` header on the request.

While this isn't specifically a problem since the only things we embed are trusted vendors, in the case of SaaS, it's better to just avoid it.

My proposal is to keep the same url, but this endpoint does the following:

* Accepts the request, and puts the token either into the browser session or browser's cookie.
* Redirect away from this url, to a generic password reset form page.
* This new password reset form page then takes the values from your session or cookie.

Doing this mitigates any ability for leaking data through any Referer header in the future.

I am open to other proposals, but this is the best that comes to my mind.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/sentry/web/decorators.py`
Content:
```
1 from __future__ import absolute_import
2 
3 from functools import wraps
4 from django.core.urlresolvers import reverse
5 from django.http import HttpResponseRedirect
6 from django.contrib import messages
7 from django.utils.translation import ugettext_lazy as _
8 
9 from sentry.utils import auth
10 from sentry_sdk import Hub
11 
12 ERR_BAD_SIGNATURE = _("The link you followed is invalid or expired.")
13 
14 
15 def login_required(func):
16     @wraps(func)
17     def wrapped(request, *args, **kwargs):
18         if not request.user.is_authenticated():
19             auth.initiate_login(request, next_url=request.get_full_path())
20             if "organization_slug" in kwargs:
21                 redirect_uri = reverse(
22                     "sentry-auth-organization", args=[kwargs["organization_slug"]]
23                 )
24             else:
25                 redirect_uri = auth.get_login_url()
26             return HttpResponseRedirect(redirect_uri)
27         return func(request, *args, **kwargs)
28 
29     return wrapped
30 
31 
32 def signed_auth_required(func):
33     @wraps(func)
34     def wrapped(request, *args, **kwargs):
35         if not request.user_from_signed_request:
36             messages.add_message(request, messages.ERROR, ERR_BAD_SIGNATURE)
37             return HttpResponseRedirect(auth.get_login_url())
38         return func(request, *args, **kwargs)
39 
40     return wrapped
41 
42 
43 def transaction_start(endpoint):
44     def decorator(func):
45         @wraps(func)
46         def wrapped(request, *args, **kwargs):
47             with Hub.current.start_transaction(op="http.server", name=endpoint, sampled=True):
48                 return func(request, *args, **kwargs)
49 
50         return wrapped
51 
52     return decorator
53
```

Path: `src/sentry/web/frontend/accounts.py`
Content:
```
1 from __future__ import absolute_import
2 
3 import logging
4 from functools import partial, update_wrapper
5 
6 from django.contrib import messages
7 from django.contrib.auth import login as login_user, authenticate
8 from django.template.context_processors import csrf
9 from django.core.urlresolvers import reverse
10 from django.db import transaction
11 from django.http import HttpResponseRedirect, Http404, HttpResponse
12 from django.views.decorators.http import require_http_methods
13 from django.views.decorators.cache import never_cache
14 from django.views.decorators.csrf import csrf_protect
15 from django.utils.translation import ugettext as _
16 
17 from sentry.models import UserEmail, LostPasswordHash, Project, UserOption, Authenticator
18 from sentry.security import capture_security_activity
19 from sentry.signals import email_verified
20 from sentry.web.decorators import login_required, signed_auth_required
21 from sentry.web.forms.accounts import RecoverPasswordForm, ChangePasswordRecoverForm
22 from sentry.web.helpers import render_to_response
23 from sentry.utils import auth
24 
25 logger = logging.getLogger("sentry.accounts")
26 
27 
28 @login_required
29 def login_redirect(request):
30     login_url = auth.get_login_redirect(request)
31     return HttpResponseRedirect(login_url)
32 
33 
34 def expired(request, user):
35     password_hash = LostPasswordHash.for_user(user)
36     password_hash.send_email(request)
37 
38     context = {"email": password_hash.user.email}
39     return render_to_response("sentry/account/recover/expired.html", context, request)
40 
41 
42 def recover(request):
43     from sentry.app import ratelimiter
44 
45     extra = {
46         "ip_address": request.META["REMOTE_ADDR"],
47         "user_agent": request.META.get("HTTP_USER_AGENT"),
48     }
49 
50     if request.method == "POST" and ratelimiter.is_limited(
51         u"accounts:recover:{}".format(extra["ip_address"]),
52         limit=5,
53         window=60,  # 5 per minute should be enough for anyone
54     ):
55         logger.warning("recover.rate-limited", extra=extra)
56 
57         return HttpResponse(
58             "You have made too many password recovery attempts. Please try again later.",
59             content_type="text/plain",
60             status=429,
61         )
62 
63     prefill = {"user": request.GET.get("email")}
64 
65     form = RecoverPasswordForm(request.POST or None, initial=prefill)
66     extra["user_recovered"] = form.data.get("user")
67 
68     if form.is_valid():
69         email = form.cleaned_data["user"]
70         if email:
71             password_hash = LostPasswordHash.for_user(email)
72             password_hash.send_email(request)
73 
74             extra["passwordhash_id"] = password_hash.id
75             extra["user_id"] = password_hash.user_id
76 
77             logger.info("recover.sent", extra=extra)
78 
79         tpl = "sentry/account/recover/sent.html"
80         context = {"email": email}
81 
82         return render_to_response(tpl, context, request)
83 
84     if form._errors:
85         logger.warning("recover.error", extra=extra)
86 
87     tpl = "sentry/account/recover/index.html"
88     context = {"form": form}
89 
90     return render_to_response(tpl, context, request)
91 
92 
93 def recover_confirm(request, user_id, hash, mode="recover"):
94     try:
95         password_hash = LostPasswordHash.objects.get(user=user_id, hash=hash)
96         if not password_hash.is_valid():
97             password_hash.delete()
98             raise LostPasswordHash.DoesNotExist
99         user = password_hash.user
100 
101     except LostPasswordHash.DoesNotExist:
102         return render_to_response(u"sentry/account/{}/{}.html".format(mode, "failure"), {}, request)
103 
104     if request.method == "POST":
105         form = ChangePasswordRecoverForm(request.POST)
106         if form.is_valid():
107             with transaction.atomic():
108                 user.set_password(form.cleaned_data["password"])
109                 user.refresh_session_nonce(request)
110                 user.save()
111 
112                 # Ugly way of doing this, but Django requires the backend be set
113                 user = authenticate(username=user.username, password=form.cleaned_data["password"])
114 
115                 # Only log the user in if there is no two-factor on the
116                 # account.
117                 if not Authenticator.objects.user_has_2fa(user):
118                     login_user(request, user)
119 
120                 password_hash.delete()
121 
122                 capture_security_activity(
123                     account=user,
124                     type="password-changed",
125                     actor=request.user,
126                     ip_address=request.META["REMOTE_ADDR"],
127                     send_email=True,
128                 )
129 
130             return login_redirect(request)
131     else:
132         form = ChangePasswordRecoverForm()
133 
134     return render_to_response(
135         u"sentry/account/{}/{}.html".format(mode, "confirm"), {"form": form}, request
136     )
137 
138 
139 # Set password variation of password recovery
140 set_password_confirm = partial(recover_confirm, mode="set_password")
141 set_password_confirm = update_wrapper(set_password_confirm, recover)
142 
143 
144 @login_required
145 @require_http_methods(["POST"])
146 def start_confirm_email(request):
147     from sentry.app import ratelimiter
148 
149     if ratelimiter.is_limited(
150         u"auth:confirm-email:{}".format(request.user.id),
151         limit=10,
152         window=60,  # 10 per minute should be enough for anyone
153     ):
154         return HttpResponse(
155             "You have made too many email confirmation requests. Please try again later.",
156             content_type="text/plain",
157             status=429,
158         )
159 
160     if "primary-email" in request.POST:
161         email = request.POST.get("email")
162         try:
163             email_to_send = UserEmail.objects.get(user=request.user, email=email)
164         except UserEmail.DoesNotExist:
165             msg = _("There was an error confirming your email.")
166             level = messages.ERROR
167         else:
168             request.user.send_confirm_email_singular(email_to_send)
169             msg = _("A verification email has been sent to %s.") % (email)
170             level = messages.SUCCESS
171         messages.add_message(request, level, msg)
172         return HttpResponseRedirect(reverse("sentry-account-settings"))
173     elif request.user.has_unverified_emails():
174         request.user.send_confirm_emails()
175         unverified_emails = [e.email for e in request.user.get_unverified_emails()]
176         msg = _("A verification email has been sent to %s.") % (", ").join(unverified_emails)
177         for email in unverified_emails:
178             logger.info(
179                 "user.email.start_confirm",
180                 extra={
181                     "user_id": request.user.id,
182                     "ip_address": request.META["REMOTE_ADDR"],
183                     "email": email,
184                 },
185             )
186     else:
187         msg = _("Your email (%s) has already been verified.") % request.user.email
188     messages.add_message(request, messages.SUCCESS, msg)
189     return HttpResponseRedirect(reverse("sentry-account-settings-emails"))
190 
191 
192 def confirm_email(request, user_id, hash):
193     msg = _("Thanks for confirming your email")
194     level = messages.SUCCESS
195     try:
196         email = UserEmail.objects.get(user=user_id, validation_hash=hash)
197         if not email.hash_is_valid():
198             raise UserEmail.DoesNotExist
199     except UserEmail.DoesNotExist:
200         if request.user.is_anonymous() or request.user.has_unverified_emails():
201             msg = _(
202                 "There was an error confirming your email. Please try again or "
203                 "visit your Account Settings to resend the verification email."
204             )
205             level = messages.ERROR
206     else:
207         email.is_verified = True
208         email.validation_hash = ""
209         email.save()
210         email_verified.send(email=email.email, sender=email)
211         logger.info(
212             "user.email.confirm",
213             extra={
214                 "user_id": user_id,
215                 "ip_address": request.META["REMOTE_ADDR"],
216                 "email": email.email,
217             },
218         )
219     messages.add_message(request, level, msg)
220     return HttpResponseRedirect(reverse("sentry-account-settings-emails"))
221 
222 
223 @csrf_protect
224 @never_cache
225 @signed_auth_required
226 @transaction.atomic
227 def email_unsubscribe_project(request, project_id):
228     # For now we only support getting here from the signed link.
229     if not request.user_from_signed_request:
230         raise Http404()
231     try:
232         project = Project.objects.get(pk=project_id)
233     except Project.DoesNotExist:
234         raise Http404()
235 
236     if request.method == "POST":
237         if "cancel" not in request.POST:
238             UserOption.objects.set_value(
239                 user=request.user, key="mail:alert", value=0, project=project
240             )
241         return HttpResponseRedirect(auth.get_login_url())
242 
243     context = csrf(request)
244     context["project"] = project
245     return render_to_response("sentry/account/email_unsubscribe_project.html", context, request)
246
```
--- 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/src/sentry/web/decorators.py b/src/sentry/web/decorators.py
--- a/src/sentry/web/decorators.py
+++ b/src/sentry/web/decorators.py
@@ -40,6 +40,19 @@
     return wrapped
 
 
+def set_referrer_policy(policy):
+    def real_decorator(func):
+        @wraps(func)
+        def wrapped(request, *args, **kwargs):
+            response = func(request, *args, **kwargs)
+            response["Referrer-Policy"] = policy
+            return response
+
+        return wrapped
+
+    return real_decorator
+
+
 def transaction_start(endpoint):
     def decorator(func):
         @wraps(func)
diff --git a/src/sentry/web/frontend/accounts.py b/src/sentry/web/frontend/accounts.py
--- a/src/sentry/web/frontend/accounts.py
+++ b/src/sentry/web/frontend/accounts.py
@@ -17,7 +17,7 @@
 from sentry.models import UserEmail, LostPasswordHash, Project, UserOption, Authenticator
 from sentry.security import capture_security_activity
 from sentry.signals import email_verified
-from sentry.web.decorators import login_required, signed_auth_required
+from sentry.web.decorators import login_required, signed_auth_required, set_referrer_policy
 from sentry.web.forms.accounts import RecoverPasswordForm, ChangePasswordRecoverForm
 from sentry.web.helpers import render_to_response
 from sentry.utils import auth
@@ -25,6 +25,10 @@
 logger = logging.getLogger("sentry.accounts")
 
 
+def get_template(mode, name):
+    return u"sentry/account/{}/{}.html".format(mode, name)
+
+
 @login_required
 def login_redirect(request):
     login_url = auth.get_login_redirect(request)
@@ -36,7 +40,7 @@
     password_hash.send_email(request)
 
     context = {"email": password_hash.user.email}
-    return render_to_response("sentry/account/recover/expired.html", context, request)
+    return render_to_response(get_template("recover", "expired"), context, request)
 
 
 def recover(request):
@@ -76,20 +80,19 @@
 
             logger.info("recover.sent", extra=extra)
 
-        tpl = "sentry/account/recover/sent.html"
         context = {"email": email}
 
-        return render_to_response(tpl, context, request)
+        return render_to_response(get_template("recover", "sent"), context, request)
 
     if form._errors:
         logger.warning("recover.error", extra=extra)
 
-    tpl = "sentry/account/recover/index.html"
     context = {"form": form}
 
-    return render_to_response(tpl, context, request)
+    return render_to_response(get_template("recover", "index"), context, request)
 
 
+@set_referrer_policy("strict-origin-when-cross-origin")
 def recover_confirm(request, user_id, hash, mode="recover"):
     try:
         password_hash = LostPasswordHash.objects.get(user=user_id, hash=hash)
@@ -99,7 +102,7 @@
         user = password_hash.user
 
     except LostPasswordHash.DoesNotExist:
-        return render_to_response(u"sentry/account/{}/{}.html".format(mode, "failure"), {}, request)
+        return render_to_response(get_template(mode, "failure"), {}, request)
 
     if request.method == "POST":
         form = ChangePasswordRecoverForm(request.POST)
@@ -131,9 +134,7 @@
     else:
         form = ChangePasswordRecoverForm()
 
-    return render_to_response(
-        u"sentry/account/{}/{}.html".format(mode, "confirm"), {"form": form}, request
-    )
+    return render_to_response(get_template(mode, "confirm"), {"form": form}, request)
 
 
 # Set password variation of password recovery
@@ -189,6 +190,7 @@
     return HttpResponseRedirect(reverse("sentry-account-settings-emails"))
 
 
+@set_referrer_policy("strict-origin-when-cross-origin")
 def confirm_email(request, user_id, hash):
     msg = _("Thanks for confirming your email")
     level = messages.SUCCESS

{"golden_diff": "diff --git a/src/sentry/web/decorators.py b/src/sentry/web/decorators.py\n--- a/src/sentry/web/decorators.py\n+++ b/src/sentry/web/decorators.py\n@@ -40,6 +40,19 @@\n     return wrapped\n \n \n+def set_referrer_policy(policy):\n+    def real_decorator(func):\n+        @wraps(func)\n+        def wrapped(request, *args, **kwargs):\n+            response = func(request, *args, **kwargs)\n+            response[\"Referrer-Policy\"] = policy\n+            return response\n+\n+        return wrapped\n+\n+    return real_decorator\n+\n+\n def transaction_start(endpoint):\n     def decorator(func):\n         @wraps(func)\ndiff --git a/src/sentry/web/frontend/accounts.py b/src/sentry/web/frontend/accounts.py\n--- a/src/sentry/web/frontend/accounts.py\n+++ b/src/sentry/web/frontend/accounts.py\n@@ -17,7 +17,7 @@\n from sentry.models import UserEmail, LostPasswordHash, Project, UserOption, Authenticator\n from sentry.security import capture_security_activity\n from sentry.signals import email_verified\n-from sentry.web.decorators import login_required, signed_auth_required\n+from sentry.web.decorators import login_required, signed_auth_required, set_referrer_policy\n from sentry.web.forms.accounts import RecoverPasswordForm, ChangePasswordRecoverForm\n from sentry.web.helpers import render_to_response\n from sentry.utils import auth\n@@ -25,6 +25,10 @@\n logger = logging.getLogger(\"sentry.accounts\")\n \n \n+def get_template(mode, name):\n+    return u\"sentry/account/{}/{}.html\".format(mode, name)\n+\n+\n @login_required\n def login_redirect(request):\n     login_url = auth.get_login_redirect(request)\n@@ -36,7 +40,7 @@\n     password_hash.send_email(request)\n \n     context = {\"email\": password_hash.user.email}\n-    return render_to_response(\"sentry/account/recover/expired.html\", context, request)\n+    return render_to_response(get_template(\"recover\", \"expired\"), context, request)\n \n \n def recover(request):\n@@ -76,20 +80,19 @@\n \n             logger.info(\"recover.sent\", extra=extra)\n \n-        tpl = \"sentry/account/recover/sent.html\"\n         context = {\"email\": email}\n \n-        return render_to_response(tpl, context, request)\n+        return render_to_response(get_template(\"recover\", \"sent\"), context, request)\n \n     if form._errors:\n         logger.warning(\"recover.error\", extra=extra)\n \n-    tpl = \"sentry/account/recover/index.html\"\n     context = {\"form\": form}\n \n-    return render_to_response(tpl, context, request)\n+    return render_to_response(get_template(\"recover\", \"index\"), context, request)\n \n \n+@set_referrer_policy(\"strict-origin-when-cross-origin\")\n def recover_confirm(request, user_id, hash, mode=\"recover\"):\n     try:\n         password_hash = LostPasswordHash.objects.get(user=user_id, hash=hash)\n@@ -99,7 +102,7 @@\n         user = password_hash.user\n \n     except LostPasswordHash.DoesNotExist:\n-        return render_to_response(u\"sentry/account/{}/{}.html\".format(mode, \"failure\"), {}, request)\n+        return render_to_response(get_template(mode, \"failure\"), {}, request)\n \n     if request.method == \"POST\":\n         form = ChangePasswordRecoverForm(request.POST)\n@@ -131,9 +134,7 @@\n     else:\n         form = ChangePasswordRecoverForm()\n \n-    return render_to_response(\n-        u\"sentry/account/{}/{}.html\".format(mode, \"confirm\"), {\"form\": form}, request\n-    )\n+    return render_to_response(get_template(mode, \"confirm\"), {\"form\": form}, request)\n \n \n # Set password variation of password recovery\n@@ -189,6 +190,7 @@\n     return HttpResponseRedirect(reverse(\"sentry-account-settings-emails\"))\n \n \n+@set_referrer_policy(\"strict-origin-when-cross-origin\")\n def confirm_email(request, user_id, hash):\n     msg = _(\"Thanks for confirming your email\")\n     level = messages.SUCCESS\n", "issue": "Prevent Referer leaking on password reset page\nRight now when you get a password reset link, the page you end up on contains the reset token in the url.\r\n\r\nDoing this causes a problem in which any page redirected to after this, and _especially_ any third party assets being loaded leak this reset token via the `Referer` header on the request.\r\n\r\nWhile this isn't specifically a problem since the only things we embed are trusted vendors, in the case of SaaS, it's better to just avoid it.\r\n\r\nMy proposal is to keep the same url, but this endpoint does the following:\r\n\r\n* Accepts the request, and puts the token either into the browser session or browser's cookie.\r\n* Redirect away from this url, to a generic password reset form page.\r\n* This new password reset form page then takes the values from your session or cookie.\r\n\r\nDoing this mitigates any ability for leaking data through any Referer header in the future.\r\n\r\nI am open to other proposals, but this is the best that comes to my mind.\n", "before_files": [{"content": "from __future__ import absolute_import\n\nfrom functools import wraps\nfrom django.core.urlresolvers import reverse\nfrom django.http import HttpResponseRedirect\nfrom django.contrib import messages\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom sentry.utils import auth\nfrom sentry_sdk import Hub\n\nERR_BAD_SIGNATURE = _(\"The link you followed is invalid or expired.\")\n\n\ndef login_required(func):\n    @wraps(func)\n    def wrapped(request, *args, **kwargs):\n        if not request.user.is_authenticated():\n            auth.initiate_login(request, next_url=request.get_full_path())\n            if \"organization_slug\" in kwargs:\n                redirect_uri = reverse(\n                    \"sentry-auth-organization\", args=[kwargs[\"organization_slug\"]]\n                )\n            else:\n                redirect_uri = auth.get_login_url()\n            return HttpResponseRedirect(redirect_uri)\n        return func(request, *args, **kwargs)\n\n    return wrapped\n\n\ndef signed_auth_required(func):\n    @wraps(func)\n    def wrapped(request, *args, **kwargs):\n        if not request.user_from_signed_request:\n            messages.add_message(request, messages.ERROR, ERR_BAD_SIGNATURE)\n            return HttpResponseRedirect(auth.get_login_url())\n        return func(request, *args, **kwargs)\n\n    return wrapped\n\n\ndef transaction_start(endpoint):\n    def decorator(func):\n        @wraps(func)\n        def wrapped(request, *args, **kwargs):\n            with Hub.current.start_transaction(op=\"http.server\", name=endpoint, sampled=True):\n                return func(request, *args, **kwargs)\n\n        return wrapped\n\n    return decorator\n", "path": "src/sentry/web/decorators.py"}, {"content": "from __future__ import absolute_import\n\nimport logging\nfrom functools import partial, update_wrapper\n\nfrom django.contrib import messages\nfrom django.contrib.auth import login as login_user, authenticate\nfrom django.template.context_processors import csrf\nfrom django.core.urlresolvers import reverse\nfrom django.db import transaction\nfrom django.http import HttpResponseRedirect, Http404, HttpResponse\nfrom django.views.decorators.http import require_http_methods\nfrom django.views.decorators.cache import never_cache\nfrom django.views.decorators.csrf import csrf_protect\nfrom django.utils.translation import ugettext as _\n\nfrom sentry.models import UserEmail, LostPasswordHash, Project, UserOption, Authenticator\nfrom sentry.security import capture_security_activity\nfrom sentry.signals import email_verified\nfrom sentry.web.decorators import login_required, signed_auth_required\nfrom sentry.web.forms.accounts import RecoverPasswordForm, ChangePasswordRecoverForm\nfrom sentry.web.helpers import render_to_response\nfrom sentry.utils import auth\n\nlogger = logging.getLogger(\"sentry.accounts\")\n\n\n@login_required\ndef login_redirect(request):\n    login_url = auth.get_login_redirect(request)\n    return HttpResponseRedirect(login_url)\n\n\ndef expired(request, user):\n    password_hash = LostPasswordHash.for_user(user)\n    password_hash.send_email(request)\n\n    context = {\"email\": password_hash.user.email}\n    return render_to_response(\"sentry/account/recover/expired.html\", context, request)\n\n\ndef recover(request):\n    from sentry.app import ratelimiter\n\n    extra = {\n        \"ip_address\": request.META[\"REMOTE_ADDR\"],\n        \"user_agent\": request.META.get(\"HTTP_USER_AGENT\"),\n    }\n\n    if request.method == \"POST\" and ratelimiter.is_limited(\n        u\"accounts:recover:{}\".format(extra[\"ip_address\"]),\n        limit=5,\n        window=60,  # 5 per minute should be enough for anyone\n    ):\n        logger.warning(\"recover.rate-limited\", extra=extra)\n\n        return HttpResponse(\n            \"You have made too many password recovery attempts. Please try again later.\",\n            content_type=\"text/plain\",\n            status=429,\n        )\n\n    prefill = {\"user\": request.GET.get(\"email\")}\n\n    form = RecoverPasswordForm(request.POST or None, initial=prefill)\n    extra[\"user_recovered\"] = form.data.get(\"user\")\n\n    if form.is_valid():\n        email = form.cleaned_data[\"user\"]\n        if email:\n            password_hash = LostPasswordHash.for_user(email)\n            password_hash.send_email(request)\n\n            extra[\"passwordhash_id\"] = password_hash.id\n            extra[\"user_id\"] = password_hash.user_id\n\n            logger.info(\"recover.sent\", extra=extra)\n\n        tpl = \"sentry/account/recover/sent.html\"\n        context = {\"email\": email}\n\n        return render_to_response(tpl, context, request)\n\n    if form._errors:\n        logger.warning(\"recover.error\", extra=extra)\n\n    tpl = \"sentry/account/recover/index.html\"\n    context = {\"form\": form}\n\n    return render_to_response(tpl, context, request)\n\n\ndef recover_confirm(request, user_id, hash, mode=\"recover\"):\n    try:\n        password_hash = LostPasswordHash.objects.get(user=user_id, hash=hash)\n        if not password_hash.is_valid():\n            password_hash.delete()\n            raise LostPasswordHash.DoesNotExist\n        user = password_hash.user\n\n    except LostPasswordHash.DoesNotExist:\n        return render_to_response(u\"sentry/account/{}/{}.html\".format(mode, \"failure\"), {}, request)\n\n    if request.method == \"POST\":\n        form = ChangePasswordRecoverForm(request.POST)\n        if form.is_valid():\n            with transaction.atomic():\n                user.set_password(form.cleaned_data[\"password\"])\n                user.refresh_session_nonce(request)\n                user.save()\n\n                # Ugly way of doing this, but Django requires the backend be set\n                user = authenticate(username=user.username, password=form.cleaned_data[\"password\"])\n\n                # Only log the user in if there is no two-factor on the\n                # account.\n                if not Authenticator.objects.user_has_2fa(user):\n                    login_user(request, user)\n\n                password_hash.delete()\n\n                capture_security_activity(\n                    account=user,\n                    type=\"password-changed\",\n                    actor=request.user,\n                    ip_address=request.META[\"REMOTE_ADDR\"],\n                    send_email=True,\n                )\n\n            return login_redirect(request)\n    else:\n        form = ChangePasswordRecoverForm()\n\n    return render_to_response(\n        u\"sentry/account/{}/{}.html\".format(mode, \"confirm\"), {\"form\": form}, request\n    )\n\n\n# Set password variation of password recovery\nset_password_confirm = partial(recover_confirm, mode=\"set_password\")\nset_password_confirm = update_wrapper(set_password_confirm, recover)\n\n\n@login_required\n@require_http_methods([\"POST\"])\ndef start_confirm_email(request):\n    from sentry.app import ratelimiter\n\n    if ratelimiter.is_limited(\n        u\"auth:confirm-email:{}\".format(request.user.id),\n        limit=10,\n        window=60,  # 10 per minute should be enough for anyone\n    ):\n        return HttpResponse(\n            \"You have made too many email confirmation requests. Please try again later.\",\n            content_type=\"text/plain\",\n            status=429,\n        )\n\n    if \"primary-email\" in request.POST:\n        email = request.POST.get(\"email\")\n        try:\n            email_to_send = UserEmail.objects.get(user=request.user, email=email)\n        except UserEmail.DoesNotExist:\n            msg = _(\"There was an error confirming your email.\")\n            level = messages.ERROR\n        else:\n            request.user.send_confirm_email_singular(email_to_send)\n            msg = _(\"A verification email has been sent to %s.\") % (email)\n            level = messages.SUCCESS\n        messages.add_message(request, level, msg)\n        return HttpResponseRedirect(reverse(\"sentry-account-settings\"))\n    elif request.user.has_unverified_emails():\n        request.user.send_confirm_emails()\n        unverified_emails = [e.email for e in request.user.get_unverified_emails()]\n        msg = _(\"A verification email has been sent to %s.\") % (\", \").join(unverified_emails)\n        for email in unverified_emails:\n            logger.info(\n                \"user.email.start_confirm\",\n                extra={\n                    \"user_id\": request.user.id,\n                    \"ip_address\": request.META[\"REMOTE_ADDR\"],\n                    \"email\": email,\n                },\n            )\n    else:\n        msg = _(\"Your email (%s) has already been verified.\") % request.user.email\n    messages.add_message(request, messages.SUCCESS, msg)\n    return HttpResponseRedirect(reverse(\"sentry-account-settings-emails\"))\n\n\ndef confirm_email(request, user_id, hash):\n    msg = _(\"Thanks for confirming your email\")\n    level = messages.SUCCESS\n    try:\n        email = UserEmail.objects.get(user=user_id, validation_hash=hash)\n        if not email.hash_is_valid():\n            raise UserEmail.DoesNotExist\n    except UserEmail.DoesNotExist:\n        if request.user.is_anonymous() or request.user.has_unverified_emails():\n            msg = _(\n                \"There was an error confirming your email. Please try again or \"\n                \"visit your Account Settings to resend the verification email.\"\n            )\n            level = messages.ERROR\n    else:\n        email.is_verified = True\n        email.validation_hash = \"\"\n        email.save()\n        email_verified.send(email=email.email, sender=email)\n        logger.info(\n            \"user.email.confirm\",\n            extra={\n                \"user_id\": user_id,\n                \"ip_address\": request.META[\"REMOTE_ADDR\"],\n                \"email\": email.email,\n            },\n        )\n    messages.add_message(request, level, msg)\n    return HttpResponseRedirect(reverse(\"sentry-account-settings-emails\"))\n\n\n@csrf_protect\n@never_cache\n@signed_auth_required\n@transaction.atomic\ndef email_unsubscribe_project(request, project_id):\n    # For now we only support getting here from the signed link.\n    if not request.user_from_signed_request:\n        raise Http404()\n    try:\n        project = Project.objects.get(pk=project_id)\n    except Project.DoesNotExist:\n        raise Http404()\n\n    if request.method == \"POST\":\n        if \"cancel\" not in request.POST:\n            UserOption.objects.set_value(\n                user=request.user, key=\"mail:alert\", value=0, project=project\n            )\n        return HttpResponseRedirect(auth.get_login_url())\n\n    context = csrf(request)\n    context[\"project\"] = project\n    return render_to_response(\"sentry/account/email_unsubscribe_project.html\", context, request)\n", "path": "src/sentry/web/frontend/accounts.py"}], "after_files": [{"content": "from __future__ import absolute_import\n\nfrom functools import wraps\nfrom django.core.urlresolvers import reverse\nfrom django.http import HttpResponseRedirect\nfrom django.contrib import messages\nfrom django.utils.translation import ugettext_lazy as _\n\nfrom sentry.utils import auth\nfrom sentry_sdk import Hub\n\nERR_BAD_SIGNATURE = _(\"The link you followed is invalid or expired.\")\n\n\ndef login_required(func):\n    @wraps(func)\n    def wrapped(request, *args, **kwargs):\n        if not request.user.is_authenticated():\n            auth.initiate_login(request, next_url=request.get_full_path())\n            if \"organization_slug\" in kwargs:\n                redirect_uri = reverse(\n                    \"sentry-auth-organization\", args=[kwargs[\"organization_slug\"]]\n                )\n            else:\n                redirect_uri = auth.get_login_url()\n            return HttpResponseRedirect(redirect_uri)\n        return func(request, *args, **kwargs)\n\n    return wrapped\n\n\ndef signed_auth_required(func):\n    @wraps(func)\n    def wrapped(request, *args, **kwargs):\n        if not request.user_from_signed_request:\n            messages.add_message(request, messages.ERROR, ERR_BAD_SIGNATURE)\n            return HttpResponseRedirect(auth.get_login_url())\n        return func(request, *args, **kwargs)\n\n    return wrapped\n\n\ndef set_referrer_policy(policy):\n    def real_decorator(func):\n        @wraps(func)\n        def wrapped(request, *args, **kwargs):\n            response = func(request, *args, **kwargs)\n            response[\"Referrer-Policy\"] = policy\n            return response\n\n        return wrapped\n\n    return real_decorator\n\n\ndef transaction_start(endpoint):\n    def decorator(func):\n        @wraps(func)\n        def wrapped(request, *args, **kwargs):\n            with Hub.current.start_transaction(op=\"http.server\", name=endpoint, sampled=True):\n                return func(request, *args, **kwargs)\n\n        return wrapped\n\n    return decorator\n", "path": "src/sentry/web/decorators.py"}, {"content": "from __future__ import absolute_import\n\nimport logging\nfrom functools import partial, update_wrapper\n\nfrom django.contrib import messages\nfrom django.contrib.auth import login as login_user, authenticate\nfrom django.template.context_processors import csrf\nfrom django.core.urlresolvers import reverse\nfrom django.db import transaction\nfrom django.http import HttpResponseRedirect, Http404, HttpResponse\nfrom django.views.decorators.http import require_http_methods\nfrom django.views.decorators.cache import never_cache\nfrom django.views.decorators.csrf import csrf_protect\nfrom django.utils.translation import ugettext as _\n\nfrom sentry.models import UserEmail, LostPasswordHash, Project, UserOption, Authenticator\nfrom sentry.security import capture_security_activity\nfrom sentry.signals import email_verified\nfrom sentry.web.decorators import login_required, signed_auth_required, set_referrer_policy\nfrom sentry.web.forms.accounts import RecoverPasswordForm, ChangePasswordRecoverForm\nfrom sentry.web.helpers import render_to_response\nfrom sentry.utils import auth\n\nlogger = logging.getLogger(\"sentry.accounts\")\n\n\ndef get_template(mode, name):\n    return u\"sentry/account/{}/{}.html\".format(mode, name)\n\n\n@login_required\ndef login_redirect(request):\n    login_url = auth.get_login_redirect(request)\n    return HttpResponseRedirect(login_url)\n\n\ndef expired(request, user):\n    password_hash = LostPasswordHash.for_user(user)\n    password_hash.send_email(request)\n\n    context = {\"email\": password_hash.user.email}\n    return render_to_response(get_template(\"recover\", \"expired\"), context, request)\n\n\ndef recover(request):\n    from sentry.app import ratelimiter\n\n    extra = {\n        \"ip_address\": request.META[\"REMOTE_ADDR\"],\n        \"user_agent\": request.META.get(\"HTTP_USER_AGENT\"),\n    }\n\n    if request.method == \"POST\" and ratelimiter.is_limited(\n        u\"accounts:recover:{}\".format(extra[\"ip_address\"]),\n        limit=5,\n        window=60,  # 5 per minute should be enough for anyone\n    ):\n        logger.warning(\"recover.rate-limited\", extra=extra)\n\n        return HttpResponse(\n            \"You have made too many password recovery attempts. Please try again later.\",\n            content_type=\"text/plain\",\n            status=429,\n        )\n\n    prefill = {\"user\": request.GET.get(\"email\")}\n\n    form = RecoverPasswordForm(request.POST or None, initial=prefill)\n    extra[\"user_recovered\"] = form.data.get(\"user\")\n\n    if form.is_valid():\n        email = form.cleaned_data[\"user\"]\n        if email:\n            password_hash = LostPasswordHash.for_user(email)\n            password_hash.send_email(request)\n\n            extra[\"passwordhash_id\"] = password_hash.id\n            extra[\"user_id\"] = password_hash.user_id\n\n            logger.info(\"recover.sent\", extra=extra)\n\n        context = {\"email\": email}\n\n        return render_to_response(get_template(\"recover\", \"sent\"), context, request)\n\n    if form._errors:\n        logger.warning(\"recover.error\", extra=extra)\n\n    context = {\"form\": form}\n\n    return render_to_response(get_template(\"recover\", \"index\"), context, request)\n\n\n@set_referrer_policy(\"strict-origin-when-cross-origin\")\ndef recover_confirm(request, user_id, hash, mode=\"recover\"):\n    try:\n        password_hash = LostPasswordHash.objects.get(user=user_id, hash=hash)\n        if not password_hash.is_valid():\n            password_hash.delete()\n            raise LostPasswordHash.DoesNotExist\n        user = password_hash.user\n\n    except LostPasswordHash.DoesNotExist:\n        return render_to_response(get_template(mode, \"failure\"), {}, request)\n\n    if request.method == \"POST\":\n        form = ChangePasswordRecoverForm(request.POST)\n        if form.is_valid():\n            with transaction.atomic():\n                user.set_password(form.cleaned_data[\"password\"])\n                user.refresh_session_nonce(request)\n                user.save()\n\n                # Ugly way of doing this, but Django requires the backend be set\n                user = authenticate(username=user.username, password=form.cleaned_data[\"password\"])\n\n                # Only log the user in if there is no two-factor on the\n                # account.\n                if not Authenticator.objects.user_has_2fa(user):\n                    login_user(request, user)\n\n                password_hash.delete()\n\n                capture_security_activity(\n                    account=user,\n                    type=\"password-changed\",\n                    actor=request.user,\n                    ip_address=request.META[\"REMOTE_ADDR\"],\n                    send_email=True,\n                )\n\n            return login_redirect(request)\n    else:\n        form = ChangePasswordRecoverForm()\n\n    return render_to_response(get_template(mode, \"confirm\"), {\"form\": form}, request)\n\n\n# Set password variation of password recovery\nset_password_confirm = partial(recover_confirm, mode=\"set_password\")\nset_password_confirm = update_wrapper(set_password_confirm, recover)\n\n\n@login_required\n@require_http_methods([\"POST\"])\ndef start_confirm_email(request):\n    from sentry.app import ratelimiter\n\n    if ratelimiter.is_limited(\n        u\"auth:confirm-email:{}\".format(request.user.id),\n        limit=10,\n        window=60,  # 10 per minute should be enough for anyone\n    ):\n        return HttpResponse(\n            \"You have made too many email confirmation requests. Please try again later.\",\n            content_type=\"text/plain\",\n            status=429,\n        )\n\n    if \"primary-email\" in request.POST:\n        email = request.POST.get(\"email\")\n        try:\n            email_to_send = UserEmail.objects.get(user=request.user, email=email)\n        except UserEmail.DoesNotExist:\n            msg = _(\"There was an error confirming your email.\")\n            level = messages.ERROR\n        else:\n            request.user.send_confirm_email_singular(email_to_send)\n            msg = _(\"A verification email has been sent to %s.\") % (email)\n            level = messages.SUCCESS\n        messages.add_message(request, level, msg)\n        return HttpResponseRedirect(reverse(\"sentry-account-settings\"))\n    elif request.user.has_unverified_emails():\n        request.user.send_confirm_emails()\n        unverified_emails = [e.email for e in request.user.get_unverified_emails()]\n        msg = _(\"A verification email has been sent to %s.\") % (\", \").join(unverified_emails)\n        for email in unverified_emails:\n            logger.info(\n                \"user.email.start_confirm\",\n                extra={\n                    \"user_id\": request.user.id,\n                    \"ip_address\": request.META[\"REMOTE_ADDR\"],\n                    \"email\": email,\n                },\n            )\n    else:\n        msg = _(\"Your email (%s) has already been verified.\") % request.user.email\n    messages.add_message(request, messages.SUCCESS, msg)\n    return HttpResponseRedirect(reverse(\"sentry-account-settings-emails\"))\n\n\n@set_referrer_policy(\"strict-origin-when-cross-origin\")\ndef confirm_email(request, user_id, hash):\n    msg = _(\"Thanks for confirming your email\")\n    level = messages.SUCCESS\n    try:\n        email = UserEmail.objects.get(user=user_id, validation_hash=hash)\n        if not email.hash_is_valid():\n            raise UserEmail.DoesNotExist\n    except UserEmail.DoesNotExist:\n        if request.user.is_anonymous() or request.user.has_unverified_emails():\n            msg = _(\n                \"There was an error confirming your email. Please try again or \"\n                \"visit your Account Settings to resend the verification email.\"\n            )\n            level = messages.ERROR\n    else:\n        email.is_verified = True\n        email.validation_hash = \"\"\n        email.save()\n        email_verified.send(email=email.email, sender=email)\n        logger.info(\n            \"user.email.confirm\",\n            extra={\n                \"user_id\": user_id,\n                \"ip_address\": request.META[\"REMOTE_ADDR\"],\n                \"email\": email.email,\n            },\n        )\n    messages.add_message(request, level, msg)\n    return HttpResponseRedirect(reverse(\"sentry-account-settings-emails\"))\n\n\n@csrf_protect\n@never_cache\n@signed_auth_required\n@transaction.atomic\ndef email_unsubscribe_project(request, project_id):\n    # For now we only support getting here from the signed link.\n    if not request.user_from_signed_request:\n        raise Http404()\n    try:\n        project = Project.objects.get(pk=project_id)\n    except Project.DoesNotExist:\n        raise Http404()\n\n    if request.method == \"POST\":\n        if \"cancel\" not in request.POST:\n            UserOption.objects.set_value(\n                user=request.user, key=\"mail:alert\", value=0, project=project\n            )\n        return HttpResponseRedirect(auth.get_login_url())\n\n    context = csrf(request)\n    context[\"project\"] = project\n    return render_to_response(\"sentry/account/email_unsubscribe_project.html\", context, request)\n", "path": "src/sentry/web/frontend/accounts.py"}]}