Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 0 additions & 120 deletions .basedpyright/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -6187,94 +6187,6 @@
"lineCount": 1
}
},
{
"code": "reportUnknownParameterType",
"range": {
"startColumn": 13,
"endColumn": 20,
"lineCount": 1
}
},
{
"code": "reportMissingParameterType",
"range": {
"startColumn": 13,
"endColumn": 20,
"lineCount": 1
}
},
{
"code": "reportMissingParameterType",
"range": {
"startColumn": 22,
"endColumn": 41,
"lineCount": 1
}
},
{
"code": "reportUnknownMemberType",
"range": {
"startColumn": 11,
"endColumn": 23,
"lineCount": 1
}
},
{
"code": "reportUnknownMemberType",
"range": {
"startColumn": 11,
"endColumn": 40,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 29,
"endColumn": 36,
"lineCount": 1
}
},
{
"code": "reportUnknownVariableType",
"range": {
"startColumn": 4,
"endColumn": 15,
"lineCount": 1
}
},
{
"code": "reportUnknownMemberType",
"range": {
"startColumn": 18,
"endColumn": 30,
"lineCount": 1
}
},
{
"code": "reportUnknownMemberType",
"range": {
"startColumn": 18,
"endColumn": 34,
"lineCount": 1
}
},
{
"code": "reportUnknownMemberType",
"range": {
"startColumn": 35,
"endColumn": 46,
"lineCount": 1
}
},
{
"code": "reportUnknownMemberType",
"range": {
"startColumn": 35,
"endColumn": 50,
"lineCount": 1
}
},
{
"code": "reportMissingTypeStubs",
"range": {
Expand All @@ -6299,38 +6211,6 @@
"lineCount": 1
}
},
{
"code": "reportUnknownMemberType",
"range": {
"startColumn": 27,
"endColumn": 42,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 27,
"endColumn": 42,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 16,
"endColumn": 23,
"lineCount": 1
}
},
{
"code": "reportUnknownArgumentType",
"range": {
"startColumn": 24,
"endColumn": 35,
"lineCount": 1
}
},
{
"code": "reportUnknownParameterType",
"range": {
Expand Down
12 changes: 11 additions & 1 deletion course/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -1163,18 +1163,28 @@


@never_cache
def sign_out(request, redirect_field_name=REDIRECT_FIELD_NAME):
def sign_out(
request: HttpRequest,
redirect_field_name: str = REDIRECT_FIELD_NAME):
if not request.user.is_authenticated:
messages.add_message(request, messages.ERROR,
_("You've already signed out."))
return redirect("relate-home")

redirect_to = request.POST.get(redirect_field_name,
request.GET.get(redirect_field_name, ""))

# Ensure the user-originating redirection url is safe.
if redirect_to and not url_has_allowed_host_and_scheme(
url=redirect_to,
allowed_hosts={request.get_host()},
require_https=request.is_secure()):
redirect_to = resolve_url("relate-home")

response = None

if settings.RELATE_SIGN_IN_BY_SAML2_ENABLED:
from djangosaml2.views import _get_subject_id

Check warning

Code scanning / CodeQL

URL redirection from remote source Medium

Untrusted URL redirection depends on a
user-provided value
.
if _get_subject_id(request.session) is not None:
# skip auth_logout below, rely on djangosaml2 to complete logout
return redirect("saml2_logout")
Expand Down
32 changes: 32 additions & 0 deletions tests/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -1127,6 +1127,38 @@ def test_sign_out_with_redirect_to(self):
fetch_redirect_response=False)
self.assertSessionHasNoUserLoggedIn()

@override_settings(RELATE_SIGN_IN_BY_SAML2_ENABLED=False)
def test_sign_out_with_redirect_to_same_host(self):
with self.temporarily_switch_to_user(self.test_user):
resp = self.get_sign_out(redirect_to="https://testserver/")
self.assertRedirects(resp, "https://testserver/",
fetch_redirect_response=False)
self.assertSessionHasNoUserLoggedIn()

@override_settings(RELATE_SIGN_IN_BY_SAML2_ENABLED=False)
def test_sign_out_with_redirect_to_external_url_is_blocked(self):
with self.temporarily_switch_to_user(self.test_user):
bad_urls = (
"http://example.com",
"http://example.com/some/path",
"https://example.com",
"https://example.com/relate-login",
"ftp://example.com",
"///example.com",
"//example.com",
'javascript:alert("XSS")',
)
for bad_url in bad_urls:
with self.subTest(bad_url=bad_url):
resp = self.get_sign_out(redirect_to=bad_url)
self.assertEqual(resp.status_code, 302)
self.assertNotIn(bad_url, resp.url,
f"{bad_url} should be blocked")
self.assertRedirects(
resp, reverse("relate-home"),
fetch_redirect_response=False)
self.assertSessionHasNoUserLoggedIn()

@override_settings(RELATE_SIGN_IN_BY_SAML2_ENABLED=True)
def test_sign_out_with_saml2_enabled_no_subject_id(self):
with mock.patch("djangosaml2.views._get_subject_id") \
Expand Down